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