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