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