@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.esm.js
CHANGED
|
@@ -176,6 +176,11 @@ class ClsLoggerCore {
|
|
|
176
176
|
this.batchTimerDueAt = null;
|
|
177
177
|
this.initTs = 0;
|
|
178
178
|
this.startupDelayMs = 0;
|
|
179
|
+
this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
|
|
180
|
+
this.useIdleCallback = false;
|
|
181
|
+
this.idleTimeout = 3000;
|
|
182
|
+
this.pendingIdleCallback = null; // requestIdleCallback 的 id
|
|
183
|
+
this.visibilityCleanup = null;
|
|
179
184
|
// 参考文档:失败缓存 + 重试
|
|
180
185
|
this.failedCacheKey = 'cls_failed_logs';
|
|
181
186
|
this.failedCacheMax = 200;
|
|
@@ -199,7 +204,7 @@ class ClsLoggerCore {
|
|
|
199
204
|
}
|
|
200
205
|
return 'browser';
|
|
201
206
|
}
|
|
202
|
-
init(options) {
|
|
207
|
+
async init(options) {
|
|
203
208
|
this.initTs = Date.now();
|
|
204
209
|
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId;
|
|
205
210
|
const endpoint = options?.tencentCloud?.endpoint ?? options?.endpoint ?? this.endpoint;
|
|
@@ -208,7 +213,7 @@ class ClsLoggerCore {
|
|
|
208
213
|
if (!topicId) {
|
|
209
214
|
// eslint-disable-next-line no-console
|
|
210
215
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
211
|
-
return;
|
|
216
|
+
return false;
|
|
212
217
|
}
|
|
213
218
|
const nextEnvType = options.envType ?? this.detectEnvType();
|
|
214
219
|
// envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
|
|
@@ -245,15 +250,21 @@ class ClsLoggerCore {
|
|
|
245
250
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
246
251
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
247
252
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
253
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
254
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
255
|
+
// startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
|
|
256
|
+
this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
|
|
248
257
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
249
258
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
250
259
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
const sdkReadyPromise = this.getInstance()
|
|
261
|
+
.then(() => true)
|
|
262
|
+
.catch(() => false);
|
|
254
263
|
if (this.enabled) {
|
|
255
264
|
// 启动时尝试发送失败缓存
|
|
256
265
|
this.flushFailed();
|
|
266
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
267
|
+
this.setupVisibilityListener();
|
|
257
268
|
// 初始化后立即启动请求监听
|
|
258
269
|
this.startRequestMonitor(options.requestMonitor);
|
|
259
270
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -262,6 +273,7 @@ class ClsLoggerCore {
|
|
|
262
273
|
// 初始化后立即启动行为埋点(PV/UV/点击)
|
|
263
274
|
this.startBehaviorMonitor(options.behaviorMonitor);
|
|
264
275
|
}
|
|
276
|
+
return sdkReadyPromise;
|
|
265
277
|
}
|
|
266
278
|
getBaseFields() {
|
|
267
279
|
let auto = undefined;
|
|
@@ -290,6 +302,97 @@ class ClsLoggerCore {
|
|
|
290
302
|
return auto;
|
|
291
303
|
return undefined;
|
|
292
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* 设置页面可见性监听
|
|
307
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
308
|
+
* - pagehide: 作为移动端 fallback
|
|
309
|
+
* - 子类可覆写此方法以实现平台特定的监听(如小程序的 wx.onAppHide)
|
|
310
|
+
*/
|
|
311
|
+
setupVisibilityListener() {
|
|
312
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
313
|
+
return;
|
|
314
|
+
// 避免重复监听
|
|
315
|
+
if (this.visibilityCleanup)
|
|
316
|
+
return;
|
|
317
|
+
const handleVisibilityChange = () => {
|
|
318
|
+
if (document.visibilityState === 'hidden') {
|
|
319
|
+
// 使用微任务延迟 flush,确保 web-vitals 等第三方库的 visibilitychange 回调先执行
|
|
320
|
+
// 这样 LCP/CLS/INP 等指标能先入队,再被 flush 发送
|
|
321
|
+
// 注意:queueMicrotask 比 setTimeout(0) 更可靠,不会被延迟太久
|
|
322
|
+
queueMicrotask(() => {
|
|
323
|
+
this.flushBatchSync();
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const handlePageHide = () => {
|
|
328
|
+
// pagehide 不能延迟,因为浏览器可能立即关闭页面
|
|
329
|
+
// 但 pagehide 通常在 visibilitychange 之后触发,此时队列应该已经包含 web-vitals 指标
|
|
330
|
+
this.flushBatchSync();
|
|
331
|
+
};
|
|
332
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
333
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
334
|
+
this.visibilityCleanup = () => {
|
|
335
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
336
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
341
|
+
* - 用于页面关闭时确保数据发送
|
|
342
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
343
|
+
*/
|
|
344
|
+
flushBatchSync() {
|
|
345
|
+
if (this.memoryQueue.length === 0)
|
|
346
|
+
return;
|
|
347
|
+
// 清除定时器
|
|
348
|
+
if (this.batchTimer) {
|
|
349
|
+
try {
|
|
350
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
351
|
+
cancelIdleCallback(this.batchTimer);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
clearTimeout(this.batchTimer);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// ignore
|
|
359
|
+
}
|
|
360
|
+
this.batchTimer = null;
|
|
361
|
+
}
|
|
362
|
+
this.batchTimerDueAt = null;
|
|
363
|
+
const logs = [...this.memoryQueue];
|
|
364
|
+
this.memoryQueue = [];
|
|
365
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
366
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
367
|
+
try {
|
|
368
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
369
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
370
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
371
|
+
const success = navigator.sendBeacon(url, blob);
|
|
372
|
+
if (!success) {
|
|
373
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
374
|
+
this.cacheFailedReportLogs(logs);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
this.cacheFailedReportLogs(logs);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
383
|
+
this.cacheFailedReportLogs(logs);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* 构建 sendBeacon 的 payload
|
|
388
|
+
*/
|
|
389
|
+
buildSendBeaconPayload(logs) {
|
|
390
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
391
|
+
return JSON.stringify({
|
|
392
|
+
source: this.source,
|
|
393
|
+
logs: logList,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
293
396
|
startRequestMonitor(requestMonitor) {
|
|
294
397
|
if (this.requestMonitorStarted)
|
|
295
398
|
return;
|
|
@@ -535,32 +638,75 @@ class ClsLoggerCore {
|
|
|
535
638
|
return;
|
|
536
639
|
}
|
|
537
640
|
this.memoryQueue.push(log);
|
|
538
|
-
|
|
641
|
+
const now = Date.now();
|
|
642
|
+
// 判断是否在启动合并窗口内
|
|
643
|
+
const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
|
|
644
|
+
// 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
|
|
645
|
+
const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
|
|
646
|
+
if (this.memoryQueue.length >= effectiveMaxSize) {
|
|
539
647
|
void this.flushBatch();
|
|
540
648
|
return;
|
|
541
649
|
}
|
|
542
|
-
const now = Date.now();
|
|
543
650
|
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
544
651
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
545
652
|
if (!this.batchTimer) {
|
|
546
653
|
this.batchTimerDueAt = desiredDueAt;
|
|
654
|
+
this.scheduleFlush(desiredDelay);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
658
|
+
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
659
|
+
this.cancelScheduledFlush();
|
|
660
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
661
|
+
this.scheduleFlush(desiredDelay);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* 调度批量发送
|
|
666
|
+
* - 先使用 setTimeout 保证最小延迟(desiredDelay)
|
|
667
|
+
* - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
|
|
668
|
+
*/
|
|
669
|
+
scheduleFlush(desiredDelay) {
|
|
670
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
671
|
+
// 先 setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
|
|
672
|
+
this.batchTimer = setTimeout(() => {
|
|
673
|
+
const idleId = requestIdleCallback(() => {
|
|
674
|
+
this.pendingIdleCallback = null;
|
|
675
|
+
void this.flushBatch();
|
|
676
|
+
}, { timeout: this.idleTimeout });
|
|
677
|
+
this.pendingIdleCallback = idleId;
|
|
678
|
+
}, desiredDelay);
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
547
681
|
this.batchTimer = setTimeout(() => {
|
|
548
682
|
void this.flushBatch();
|
|
549
683
|
}, desiredDelay);
|
|
550
|
-
return;
|
|
551
684
|
}
|
|
552
|
-
|
|
553
|
-
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* 取消已调度的批量发送
|
|
688
|
+
* - 同时清理 setTimeout 和可能的 requestIdleCallback
|
|
689
|
+
*/
|
|
690
|
+
cancelScheduledFlush() {
|
|
691
|
+
// 清理 setTimeout
|
|
692
|
+
if (this.batchTimer) {
|
|
554
693
|
try {
|
|
555
694
|
clearTimeout(this.batchTimer);
|
|
556
695
|
}
|
|
557
696
|
catch {
|
|
558
697
|
// ignore
|
|
559
698
|
}
|
|
560
|
-
this.
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
699
|
+
this.batchTimer = null;
|
|
700
|
+
}
|
|
701
|
+
// 清理可能的 pendingIdleCallback
|
|
702
|
+
if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
|
|
703
|
+
try {
|
|
704
|
+
cancelIdleCallback(this.pendingIdleCallback);
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// ignore
|
|
708
|
+
}
|
|
709
|
+
this.pendingIdleCallback = null;
|
|
564
710
|
}
|
|
565
711
|
}
|
|
566
712
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
@@ -573,7 +719,7 @@ class ClsLoggerCore {
|
|
|
573
719
|
}
|
|
574
720
|
return nowTs + this.batchIntervalMs;
|
|
575
721
|
}
|
|
576
|
-
info(message, data = {}) {
|
|
722
|
+
info(message, data = {}, options) {
|
|
577
723
|
let msg = '';
|
|
578
724
|
let extra = {};
|
|
579
725
|
if (message instanceof Error) {
|
|
@@ -589,9 +735,18 @@ class ClsLoggerCore {
|
|
|
589
735
|
extra = data;
|
|
590
736
|
}
|
|
591
737
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
592
|
-
|
|
738
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
739
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
740
|
+
if (options?.immediate) {
|
|
741
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
742
|
+
this.cacheFailedReportLogs([log]);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
this.report(log);
|
|
747
|
+
}
|
|
593
748
|
}
|
|
594
|
-
warn(message, data = {}) {
|
|
749
|
+
warn(message, data = {}, options) {
|
|
595
750
|
let msg = '';
|
|
596
751
|
let extra = {};
|
|
597
752
|
if (message instanceof Error) {
|
|
@@ -607,9 +762,18 @@ class ClsLoggerCore {
|
|
|
607
762
|
extra = data;
|
|
608
763
|
}
|
|
609
764
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
610
|
-
|
|
765
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
766
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
767
|
+
if (options?.immediate) {
|
|
768
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
769
|
+
this.cacheFailedReportLogs([log]);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
this.report(log);
|
|
774
|
+
}
|
|
611
775
|
}
|
|
612
|
-
error(message, data = {}) {
|
|
776
|
+
error(message, data = {}, options) {
|
|
613
777
|
let msg = '';
|
|
614
778
|
let extra = {};
|
|
615
779
|
if (message instanceof Error) {
|
|
@@ -625,7 +789,17 @@ class ClsLoggerCore {
|
|
|
625
789
|
extra = data;
|
|
626
790
|
}
|
|
627
791
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
628
|
-
|
|
792
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
793
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
794
|
+
const immediate = options?.immediate ?? true;
|
|
795
|
+
if (immediate) {
|
|
796
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
797
|
+
this.cacheFailedReportLogs([log]);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
this.report(log);
|
|
802
|
+
}
|
|
629
803
|
}
|
|
630
804
|
track(trackType, data = {}) {
|
|
631
805
|
if (!trackType)
|
|
@@ -640,10 +814,7 @@ class ClsLoggerCore {
|
|
|
640
814
|
* 立即发送内存队列
|
|
641
815
|
*/
|
|
642
816
|
async flushBatch() {
|
|
643
|
-
|
|
644
|
-
clearTimeout(this.batchTimer);
|
|
645
|
-
this.batchTimer = null;
|
|
646
|
-
}
|
|
817
|
+
this.cancelScheduledFlush();
|
|
647
818
|
this.batchTimerDueAt = null;
|
|
648
819
|
if (this.memoryQueue.length === 0)
|
|
649
820
|
return;
|
|
@@ -749,7 +920,14 @@ class ClsLoggerCore {
|
|
|
749
920
|
// 先清空,再尝试发送
|
|
750
921
|
writeStringStorage(this.failedCacheKey, JSON.stringify([]));
|
|
751
922
|
this.memoryQueue.unshift(...logs);
|
|
752
|
-
|
|
923
|
+
// 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
|
|
924
|
+
if (!this.batchTimer) {
|
|
925
|
+
const now = Date.now();
|
|
926
|
+
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
927
|
+
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
928
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
929
|
+
this.scheduleFlush(desiredDelay);
|
|
930
|
+
}
|
|
753
931
|
}
|
|
754
932
|
/**
|
|
755
933
|
* 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
|
|
@@ -1288,6 +1466,13 @@ function installRequestMonitor(report, opts = {}) {
|
|
|
1288
1466
|
const DEFAULT_MAX_TEXT$1 = 4000;
|
|
1289
1467
|
const DEFAULT_DEDUPE_WINDOW_MS$1 = 3000;
|
|
1290
1468
|
const DEFAULT_DEDUPE_MAX_KEYS$1 = 200;
|
|
1469
|
+
/** 默认忽略的无意义错误信息 */
|
|
1470
|
+
const DEFAULT_IGNORE_MESSAGES$1 = [
|
|
1471
|
+
'Script error.',
|
|
1472
|
+
'Script error',
|
|
1473
|
+
/^ResizeObserver loop/,
|
|
1474
|
+
'Permission was denied',
|
|
1475
|
+
];
|
|
1291
1476
|
function truncate$3(s, maxLen) {
|
|
1292
1477
|
if (!s)
|
|
1293
1478
|
return s;
|
|
@@ -1300,11 +1485,22 @@ function sampleHit$3(sampleRate) {
|
|
|
1300
1485
|
return false;
|
|
1301
1486
|
return Math.random() < sampleRate;
|
|
1302
1487
|
}
|
|
1488
|
+
/** 检查消息是否应该被忽略 */
|
|
1489
|
+
function shouldIgnoreMessage$1(message, ignorePatterns) {
|
|
1490
|
+
if (!message || ignorePatterns.length === 0)
|
|
1491
|
+
return false;
|
|
1492
|
+
return ignorePatterns.some((pattern) => {
|
|
1493
|
+
if (typeof pattern === 'string') {
|
|
1494
|
+
return message === pattern || message.includes(pattern);
|
|
1495
|
+
}
|
|
1496
|
+
return pattern.test(message);
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1303
1499
|
function getPagePath$1() {
|
|
1304
1500
|
try {
|
|
1305
1501
|
if (typeof window === 'undefined')
|
|
1306
1502
|
return '';
|
|
1307
|
-
return window.location?.
|
|
1503
|
+
return window.location?.href ?? '';
|
|
1308
1504
|
}
|
|
1309
1505
|
catch {
|
|
1310
1506
|
return '';
|
|
@@ -1395,6 +1591,9 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
1395
1591
|
};
|
|
1396
1592
|
if (event && typeof event === 'object' && 'message' in event) {
|
|
1397
1593
|
payload.message = truncate$3(String(event.message ?? ''), options.maxTextLength);
|
|
1594
|
+
// 检查是否应该忽略此错误
|
|
1595
|
+
if (shouldIgnoreMessage$1(String(event.message ?? ''), options.ignoreMessages))
|
|
1596
|
+
return;
|
|
1398
1597
|
payload.filename = truncate$3(String(event.filename ?? ''), 500);
|
|
1399
1598
|
payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
|
|
1400
1599
|
payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
|
|
@@ -1433,6 +1632,9 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
1433
1632
|
return;
|
|
1434
1633
|
const reason = event?.reason;
|
|
1435
1634
|
const e = normalizeErrorLike$1(reason, options.maxTextLength);
|
|
1635
|
+
// 检查是否应该忽略此错误
|
|
1636
|
+
if (shouldIgnoreMessage$1(e.message, options.ignoreMessages))
|
|
1637
|
+
return;
|
|
1436
1638
|
const payload = {
|
|
1437
1639
|
pagePath: getPagePath$1(),
|
|
1438
1640
|
source: 'unhandledrejection',
|
|
@@ -1462,6 +1664,7 @@ function installWebErrorMonitor(report, opts = {}) {
|
|
|
1462
1664
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT$1,
|
|
1463
1665
|
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS$1,
|
|
1464
1666
|
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS$1,
|
|
1667
|
+
ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES$1,
|
|
1465
1668
|
};
|
|
1466
1669
|
installBrowserErrorMonitor(report, options);
|
|
1467
1670
|
}
|
|
@@ -1469,6 +1672,13 @@ function installWebErrorMonitor(report, opts = {}) {
|
|
|
1469
1672
|
const DEFAULT_MAX_TEXT = 4000;
|
|
1470
1673
|
const DEFAULT_DEDUPE_WINDOW_MS = 3000;
|
|
1471
1674
|
const DEFAULT_DEDUPE_MAX_KEYS = 200;
|
|
1675
|
+
/** 默认忽略的无意义错误信息 */
|
|
1676
|
+
const DEFAULT_IGNORE_MESSAGES = [
|
|
1677
|
+
'Script error.',
|
|
1678
|
+
'Script error',
|
|
1679
|
+
/^ResizeObserver loop/,
|
|
1680
|
+
'Permission was denied',
|
|
1681
|
+
];
|
|
1472
1682
|
function truncate$2(s, maxLen) {
|
|
1473
1683
|
if (!s)
|
|
1474
1684
|
return s;
|
|
@@ -1481,12 +1691,33 @@ function sampleHit$2(sampleRate) {
|
|
|
1481
1691
|
return false;
|
|
1482
1692
|
return Math.random() < sampleRate;
|
|
1483
1693
|
}
|
|
1694
|
+
/** 检查消息是否应该被忽略 */
|
|
1695
|
+
function shouldIgnoreMessage(message, ignorePatterns) {
|
|
1696
|
+
if (!message || ignorePatterns.length === 0)
|
|
1697
|
+
return false;
|
|
1698
|
+
return ignorePatterns.some((pattern) => {
|
|
1699
|
+
if (typeof pattern === 'string') {
|
|
1700
|
+
return message === pattern || message.includes(pattern);
|
|
1701
|
+
}
|
|
1702
|
+
return pattern.test(message);
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1484
1705
|
function getMpPagePath() {
|
|
1485
1706
|
try {
|
|
1486
1707
|
const pages = globalThis.getCurrentPages?.();
|
|
1487
1708
|
if (Array.isArray(pages) && pages.length > 0) {
|
|
1488
1709
|
const page = pages[pages.length - 1];
|
|
1489
|
-
|
|
1710
|
+
const route = page.route || page.__route__;
|
|
1711
|
+
if (typeof route === 'string') {
|
|
1712
|
+
let path = route.startsWith('/') ? route : `/${route}`;
|
|
1713
|
+
const options = page.options || {};
|
|
1714
|
+
const keys = Object.keys(options);
|
|
1715
|
+
if (keys.length > 0) {
|
|
1716
|
+
const qs = keys.map((k) => `${k}=${options[k]}`).join('&');
|
|
1717
|
+
path = `${path}?${qs}`;
|
|
1718
|
+
}
|
|
1719
|
+
return path;
|
|
1720
|
+
}
|
|
1490
1721
|
}
|
|
1491
1722
|
return '';
|
|
1492
1723
|
}
|
|
@@ -1575,6 +1806,9 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
1575
1806
|
if (!sampleHit$2(options.sampleRate))
|
|
1576
1807
|
return;
|
|
1577
1808
|
const e = normalizeErrorLike(msg, options.maxTextLength);
|
|
1809
|
+
// 检查是否应该忽略此错误
|
|
1810
|
+
if (shouldIgnoreMessage(e.message, options.ignoreMessages))
|
|
1811
|
+
return;
|
|
1578
1812
|
const payload = {
|
|
1579
1813
|
pagePath: getMpPagePath(),
|
|
1580
1814
|
source: 'wx.onError',
|
|
@@ -1602,6 +1836,9 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
1602
1836
|
if (!sampleHit$2(options.sampleRate))
|
|
1603
1837
|
return;
|
|
1604
1838
|
const e = normalizeErrorLike(res?.reason, options.maxTextLength);
|
|
1839
|
+
// 检查是否应该忽略此错误
|
|
1840
|
+
if (shouldIgnoreMessage(e.message, options.ignoreMessages))
|
|
1841
|
+
return;
|
|
1605
1842
|
const payload = {
|
|
1606
1843
|
pagePath: getMpPagePath(),
|
|
1607
1844
|
source: 'wx.onUnhandledRejection',
|
|
@@ -1633,15 +1870,18 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
1633
1870
|
try {
|
|
1634
1871
|
if (sampleHit$2(options.sampleRate)) {
|
|
1635
1872
|
const e = normalizeErrorLike(args?.[0], options.maxTextLength);
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1873
|
+
// 检查是否应该忽略此错误
|
|
1874
|
+
if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
|
|
1875
|
+
const payload = {
|
|
1876
|
+
pagePath: getMpPagePath(),
|
|
1877
|
+
source: 'App.onError',
|
|
1878
|
+
message: e.message,
|
|
1879
|
+
errorName: e.name,
|
|
1880
|
+
stack: e.stack,
|
|
1881
|
+
};
|
|
1882
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
1883
|
+
report(options.reportType, payload);
|
|
1884
|
+
}
|
|
1645
1885
|
}
|
|
1646
1886
|
}
|
|
1647
1887
|
catch {
|
|
@@ -1657,15 +1897,18 @@ function installMiniProgramErrorMonitor(report, options) {
|
|
|
1657
1897
|
if (sampleHit$2(options.sampleRate)) {
|
|
1658
1898
|
const reason = args?.[0]?.reason ?? args?.[0];
|
|
1659
1899
|
const e = normalizeErrorLike(reason, options.maxTextLength);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1900
|
+
// 检查是否应该忽略此错误
|
|
1901
|
+
if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
|
|
1902
|
+
const payload = {
|
|
1903
|
+
pagePath: getMpPagePath(),
|
|
1904
|
+
source: 'App.onUnhandledRejection',
|
|
1905
|
+
message: e.message,
|
|
1906
|
+
errorName: e.name,
|
|
1907
|
+
stack: e.stack,
|
|
1908
|
+
};
|
|
1909
|
+
if (shouldReport(buildErrorKey(options.reportType, payload)))
|
|
1910
|
+
report(options.reportType, payload);
|
|
1911
|
+
}
|
|
1669
1912
|
}
|
|
1670
1913
|
}
|
|
1671
1914
|
catch {
|
|
@@ -1696,6 +1939,7 @@ function installMiniErrorMonitor(report, opts = {}) {
|
|
|
1696
1939
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
|
|
1697
1940
|
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
|
|
1698
1941
|
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
|
|
1942
|
+
ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
|
|
1699
1943
|
};
|
|
1700
1944
|
installMiniProgramErrorMonitor(report, options);
|
|
1701
1945
|
}
|
|
@@ -1741,7 +1985,7 @@ function getPagePath() {
|
|
|
1741
1985
|
try {
|
|
1742
1986
|
if (typeof window === 'undefined')
|
|
1743
1987
|
return '';
|
|
1744
|
-
return window.location?.
|
|
1988
|
+
return window.location?.href ?? '';
|
|
1745
1989
|
}
|
|
1746
1990
|
catch {
|
|
1747
1991
|
return '';
|
|
@@ -1913,9 +2157,25 @@ function installBrowserPerformanceMonitor(report, options) {
|
|
|
1913
2157
|
if (!name || shouldIgnoreUrl(name, ignoreUrls))
|
|
1914
2158
|
continue;
|
|
1915
2159
|
const initiatorType = String(entry?.initiatorType ?? '');
|
|
1916
|
-
//
|
|
2160
|
+
// 关注 fetch/xhr/img/script(同时允许 css/other 但不强制)
|
|
1917
2161
|
if (!['xmlhttprequest', 'fetch', 'img', 'script', 'css'].includes(initiatorType))
|
|
1918
2162
|
continue;
|
|
2163
|
+
// 时序分解(便于定位慢在哪个阶段)
|
|
2164
|
+
const domainLookupStart = entry.domainLookupStart ?? 0;
|
|
2165
|
+
const domainLookupEnd = entry.domainLookupEnd ?? 0;
|
|
2166
|
+
const connectStart = entry.connectStart ?? 0;
|
|
2167
|
+
const connectEnd = entry.connectEnd ?? 0;
|
|
2168
|
+
const requestStart = entry.requestStart ?? 0;
|
|
2169
|
+
const responseStart = entry.responseStart ?? 0;
|
|
2170
|
+
const responseEnd = entry.responseEnd ?? 0;
|
|
2171
|
+
const dns = domainLookupEnd - domainLookupStart;
|
|
2172
|
+
const tcp = connectEnd - connectStart;
|
|
2173
|
+
const ttfb = responseStart - requestStart;
|
|
2174
|
+
const download = responseEnd - responseStart;
|
|
2175
|
+
// 缓存检测:transferSize=0 且 decodedBodySize>0 表示缓存命中
|
|
2176
|
+
const transferSize = entry.transferSize ?? 0;
|
|
2177
|
+
const decodedBodySize = entry.decodedBodySize ?? 0;
|
|
2178
|
+
const cached = transferSize === 0 && decodedBodySize > 0;
|
|
1919
2179
|
const payload = {
|
|
1920
2180
|
pagePath: getPagePath(),
|
|
1921
2181
|
metric: 'resource',
|
|
@@ -1923,16 +2183,26 @@ function installBrowserPerformanceMonitor(report, options) {
|
|
|
1923
2183
|
url: truncate$1(name, options.maxTextLength),
|
|
1924
2184
|
startTime: typeof entry?.startTime === 'number' ? entry.startTime : undefined,
|
|
1925
2185
|
duration: typeof entry?.duration === 'number' ? entry.duration : undefined,
|
|
2186
|
+
// 时序分解(跨域资源可能为 0)
|
|
2187
|
+
dns: dns > 0 ? Math.round(dns) : undefined,
|
|
2188
|
+
tcp: tcp > 0 ? Math.round(tcp) : undefined,
|
|
2189
|
+
ttfb: ttfb > 0 ? Math.round(ttfb) : undefined,
|
|
2190
|
+
download: download > 0 ? Math.round(download) : undefined,
|
|
2191
|
+
// 缓存标记
|
|
2192
|
+
cached,
|
|
1926
2193
|
};
|
|
1927
|
-
//
|
|
1928
|
-
if (
|
|
1929
|
-
payload.transferSize =
|
|
1930
|
-
if (typeof entry?.encodedBodySize === 'number')
|
|
2194
|
+
// 尺寸字段(跨域资源可能为 0)
|
|
2195
|
+
if (transferSize > 0)
|
|
2196
|
+
payload.transferSize = transferSize;
|
|
2197
|
+
if (typeof entry?.encodedBodySize === 'number' && entry.encodedBodySize > 0) {
|
|
1931
2198
|
payload.encodedBodySize = entry.encodedBodySize;
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2199
|
+
}
|
|
2200
|
+
if (decodedBodySize > 0)
|
|
2201
|
+
payload.decodedBodySize = decodedBodySize;
|
|
2202
|
+
// 协议和状态
|
|
2203
|
+
if (typeof entry?.nextHopProtocol === 'string' && entry.nextHopProtocol) {
|
|
1935
2204
|
payload.nextHopProtocol = entry.nextHopProtocol;
|
|
2205
|
+
}
|
|
1936
2206
|
if (typeof entry?.responseStatus === 'number')
|
|
1937
2207
|
payload.status = entry.responseStatus;
|
|
1938
2208
|
report(options.reportType, payload);
|
|
@@ -2109,7 +2379,7 @@ function writeUvMeta$1(key, meta) {
|
|
|
2109
2379
|
function getWebPagePath() {
|
|
2110
2380
|
if (typeof window === 'undefined')
|
|
2111
2381
|
return '';
|
|
2112
|
-
return window.location?.
|
|
2382
|
+
return window.location?.href || '';
|
|
2113
2383
|
}
|
|
2114
2384
|
function buildCommonUvFields$1(uvId, uvMeta, isFirstVisit) {
|
|
2115
2385
|
return {
|
|
@@ -2361,7 +2631,16 @@ function getMiniProgramPagePath() {
|
|
|
2361
2631
|
const pages = typeof g.getCurrentPages === 'function' ? g.getCurrentPages() : [];
|
|
2362
2632
|
const last = Array.isArray(pages) ? pages[pages.length - 1] : undefined;
|
|
2363
2633
|
const route = (last?.route || last?.__route__);
|
|
2364
|
-
|
|
2634
|
+
if (typeof route !== 'string')
|
|
2635
|
+
return '';
|
|
2636
|
+
let path = route.startsWith('/') ? route : `/${route}`;
|
|
2637
|
+
const options = last?.options || {};
|
|
2638
|
+
const keys = Object.keys(options);
|
|
2639
|
+
if (keys.length > 0) {
|
|
2640
|
+
const qs = keys.map((k) => `${k}=${options[k]}`).join('&');
|
|
2641
|
+
path = `${path}?${qs}`;
|
|
2642
|
+
}
|
|
2643
|
+
return path;
|
|
2365
2644
|
}
|
|
2366
2645
|
catch {
|
|
2367
2646
|
return '';
|
|
@@ -2709,7 +2988,7 @@ function getBrowserDeviceInfo(options) {
|
|
|
2709
2988
|
if (options.includeNetwork) {
|
|
2710
2989
|
try {
|
|
2711
2990
|
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
2712
|
-
if (conn &&
|
|
2991
|
+
if (conn && typeof conn === 'object') {
|
|
2713
2992
|
if (typeof conn.effectiveType === 'string')
|
|
2714
2993
|
out.netEffectiveType = conn.effectiveType;
|
|
2715
2994
|
if (typeof conn.downlink === 'number')
|
|
@@ -2724,6 +3003,43 @@ function getBrowserDeviceInfo(options) {
|
|
|
2724
3003
|
// ignore
|
|
2725
3004
|
}
|
|
2726
3005
|
}
|
|
3006
|
+
if (options.includeNetworkType) {
|
|
3007
|
+
try {
|
|
3008
|
+
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
3009
|
+
if (conn && typeof conn === 'object') {
|
|
3010
|
+
const type = conn.type;
|
|
3011
|
+
const eff = conn.effectiveType;
|
|
3012
|
+
let val = '';
|
|
3013
|
+
if (type === 'wifi' || type === 'ethernet') {
|
|
3014
|
+
val = 'WIFI';
|
|
3015
|
+
}
|
|
3016
|
+
else if (type === 'cellular' || type === 'wimax') {
|
|
3017
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
3018
|
+
val = '2G';
|
|
3019
|
+
else if (eff === '3g')
|
|
3020
|
+
val = '3G';
|
|
3021
|
+
else if (eff === '4g')
|
|
3022
|
+
val = '4G';
|
|
3023
|
+
else
|
|
3024
|
+
val = '4G';
|
|
3025
|
+
}
|
|
3026
|
+
else {
|
|
3027
|
+
// type 未知或不支持,降级使用 effectiveType
|
|
3028
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
3029
|
+
val = '2G';
|
|
3030
|
+
else if (eff === '3g')
|
|
3031
|
+
val = '3G';
|
|
3032
|
+
else if (eff === '4g')
|
|
3033
|
+
val = '4G';
|
|
3034
|
+
}
|
|
3035
|
+
if (val)
|
|
3036
|
+
out.networkType = val;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
catch {
|
|
3040
|
+
// ignore
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2727
3043
|
return out;
|
|
2728
3044
|
}
|
|
2729
3045
|
function createWebDeviceInfoBaseFields(opts) {
|
|
@@ -2753,7 +3069,7 @@ function createWebDeviceInfoBaseFields(opts) {
|
|
|
2753
3069
|
try {
|
|
2754
3070
|
const g = globalThis;
|
|
2755
3071
|
const extra = g[globalKey];
|
|
2756
|
-
if (extra &&
|
|
3072
|
+
if (extra && typeof extra === 'object')
|
|
2757
3073
|
return { ...base, ...extra };
|
|
2758
3074
|
}
|
|
2759
3075
|
catch {
|
|
@@ -2790,7 +3106,7 @@ function getMiniProgramDeviceInfo(options) {
|
|
|
2790
3106
|
try {
|
|
2791
3107
|
if (typeof wxAny.getNetworkTypeSync === 'function') {
|
|
2792
3108
|
const n = wxAny.getNetworkTypeSync();
|
|
2793
|
-
out.networkType = n?.networkType ? String(n.networkType) : '';
|
|
3109
|
+
out.networkType = n?.networkType ? String(n.networkType).toUpperCase() : '';
|
|
2794
3110
|
}
|
|
2795
3111
|
else if (typeof wxAny.getNetworkType === 'function') {
|
|
2796
3112
|
// 异步更新:先不阻塞初始化
|
|
@@ -2800,7 +3116,9 @@ function getMiniProgramDeviceInfo(options) {
|
|
|
2800
3116
|
const g = globalThis;
|
|
2801
3117
|
if (!g.__beLinkClsLoggerDeviceInfo__)
|
|
2802
3118
|
g.__beLinkClsLoggerDeviceInfo__ = {};
|
|
2803
|
-
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType
|
|
3119
|
+
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType
|
|
3120
|
+
? String(res.networkType).toUpperCase()
|
|
3121
|
+
: '';
|
|
2804
3122
|
}
|
|
2805
3123
|
catch {
|
|
2806
3124
|
// ignore
|
|
@@ -2819,7 +3137,7 @@ function getMiniProgramDeviceInfo(options) {
|
|
|
2819
3137
|
const g = globalThis;
|
|
2820
3138
|
if (!g.__beLinkClsLoggerDeviceInfo__)
|
|
2821
3139
|
g.__beLinkClsLoggerDeviceInfo__ = {};
|
|
2822
|
-
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType ? String(res.networkType) : '';
|
|
3140
|
+
g.__beLinkClsLoggerDeviceInfo__.networkType = res?.networkType ? String(res.networkType).toUpperCase() : '';
|
|
2823
3141
|
}
|
|
2824
3142
|
catch {
|
|
2825
3143
|
// ignore
|
|
@@ -2860,7 +3178,7 @@ function createMiniDeviceInfoBaseFields(opts) {
|
|
|
2860
3178
|
try {
|
|
2861
3179
|
const g = globalThis;
|
|
2862
3180
|
const extra = g[globalKey];
|
|
2863
|
-
if (extra &&
|
|
3181
|
+
if (extra && typeof extra === 'object')
|
|
2864
3182
|
return { ...base, ...extra };
|
|
2865
3183
|
}
|
|
2866
3184
|
catch {
|