@be-link/cls-logger 1.0.1-beta.17 → 1.0.1-beta.19

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/dist/index.umd.js CHANGED
@@ -182,6 +182,9 @@
182
182
  this.batchTimerDueAt = null;
183
183
  this.initTs = 0;
184
184
  this.startupDelayMs = 0;
185
+ this.useIdleCallback = false;
186
+ this.idleTimeout = 3000;
187
+ this.visibilityCleanup = null;
185
188
  // 参考文档:失败缓存 + 重试
186
189
  this.failedCacheKey = 'cls_failed_logs';
187
190
  this.failedCacheMax = 200;
@@ -251,6 +254,8 @@
251
254
  this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
252
255
  this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
253
256
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
257
+ this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
258
+ this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
254
259
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
255
260
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
256
261
  // 预热(避免首条日志触发 import/初始化开销)
@@ -260,6 +265,8 @@
260
265
  if (this.enabled) {
261
266
  // 启动时尝试发送失败缓存
262
267
  this.flushFailed();
268
+ // 添加页面可见性监听(确保页面关闭时数据不丢失)
269
+ this.setupVisibilityListener();
263
270
  // 初始化后立即启动请求监听
264
271
  this.startRequestMonitor(options.requestMonitor);
265
272
  // 初始化后立即启动错误监控/性能监控
@@ -296,6 +303,89 @@
296
303
  return auto;
297
304
  return undefined;
298
305
  }
306
+ /**
307
+ * 设置页面可见性监听
308
+ * - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
309
+ * - pagehide: 作为移动端 fallback
310
+ */
311
+ setupVisibilityListener() {
312
+ if (typeof document === 'undefined' || typeof window === 'undefined')
313
+ return;
314
+ // 避免重复监听
315
+ if (this.visibilityCleanup)
316
+ return;
317
+ const handleVisibilityChange = () => {
318
+ if (document.visibilityState === 'hidden') {
319
+ this.flushBatchSync();
320
+ }
321
+ };
322
+ const handlePageHide = () => {
323
+ this.flushBatchSync();
324
+ };
325
+ document.addEventListener('visibilitychange', handleVisibilityChange);
326
+ window.addEventListener('pagehide', handlePageHide);
327
+ this.visibilityCleanup = () => {
328
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
329
+ window.removeEventListener('pagehide', handlePageHide);
330
+ };
331
+ }
332
+ /**
333
+ * 同步发送内存队列(使用 sendBeacon)
334
+ * - 用于页面关闭时确保数据发送
335
+ * - sendBeacon 不可用时降级为缓存到 localStorage
336
+ */
337
+ flushBatchSync() {
338
+ if (this.memoryQueue.length === 0)
339
+ return;
340
+ // 清除定时器
341
+ if (this.batchTimer) {
342
+ try {
343
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
344
+ cancelIdleCallback(this.batchTimer);
345
+ }
346
+ else {
347
+ clearTimeout(this.batchTimer);
348
+ }
349
+ }
350
+ catch {
351
+ // ignore
352
+ }
353
+ this.batchTimer = null;
354
+ }
355
+ this.batchTimerDueAt = null;
356
+ const logs = [...this.memoryQueue];
357
+ this.memoryQueue = [];
358
+ // 优先使用 sendBeacon(页面关闭时可靠发送)
359
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
360
+ try {
361
+ const payload = this.buildSendBeaconPayload(logs);
362
+ const blob = new Blob([payload], { type: 'application/json' });
363
+ const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
364
+ const success = navigator.sendBeacon(url, blob);
365
+ if (!success) {
366
+ // sendBeacon 返回 false 时,降级缓存
367
+ this.cacheFailedReportLogs(logs);
368
+ }
369
+ }
370
+ catch {
371
+ this.cacheFailedReportLogs(logs);
372
+ }
373
+ }
374
+ else {
375
+ // 不支持 sendBeacon,降级缓存到 localStorage
376
+ this.cacheFailedReportLogs(logs);
377
+ }
378
+ }
379
+ /**
380
+ * 构建 sendBeacon 的 payload
381
+ */
382
+ buildSendBeaconPayload(logs) {
383
+ const logList = logs.map((log) => this.buildReportFields(log));
384
+ return JSON.stringify({
385
+ source: this.source,
386
+ logs: logList,
387
+ });
388
+ }
299
389
  startRequestMonitor(requestMonitor) {
300
390
  if (this.requestMonitorStarted)
301
391
  return;
@@ -550,25 +640,55 @@
550
640
  const desiredDelay = Math.max(0, desiredDueAt - now);
551
641
  if (!this.batchTimer) {
552
642
  this.batchTimerDueAt = desiredDueAt;
553
- this.batchTimer = setTimeout(() => {
554
- void this.flushBatch();
555
- }, desiredDelay);
643
+ this.scheduleFlush(desiredDelay);
556
644
  return;
557
645
  }
558
- // 启动合并窗口内:如果当前 timer 会“更早”触发,则延后到窗口结束,尽量减少多次发送
646
+ // 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
559
647
  if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
560
- try {
561
- clearTimeout(this.batchTimer);
562
- }
563
- catch {
564
- // ignore
565
- }
648
+ this.cancelScheduledFlush();
566
649
  this.batchTimerDueAt = desiredDueAt;
650
+ this.scheduleFlush(desiredDelay);
651
+ }
652
+ }
653
+ /**
654
+ * 调度批量发送
655
+ * - 支持 requestIdleCallback(浏览器空闲时执行)
656
+ * - 降级为 setTimeout
657
+ */
658
+ scheduleFlush(desiredDelay) {
659
+ if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
660
+ // 使用 requestIdleCallback,设置 timeout 保证最终执行
661
+ const idleId = requestIdleCallback(() => {
662
+ void this.flushBatch();
663
+ }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
664
+ // 存储 idleId 以便清理(类型兼容处理)
665
+ this.batchTimer = idleId;
666
+ }
667
+ else {
567
668
  this.batchTimer = setTimeout(() => {
568
669
  void this.flushBatch();
569
670
  }, desiredDelay);
570
671
  }
571
672
  }
673
+ /**
674
+ * 取消已调度的批量发送
675
+ */
676
+ cancelScheduledFlush() {
677
+ if (!this.batchTimer)
678
+ return;
679
+ try {
680
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
681
+ cancelIdleCallback(this.batchTimer);
682
+ }
683
+ else {
684
+ clearTimeout(this.batchTimer);
685
+ }
686
+ }
687
+ catch {
688
+ // ignore
689
+ }
690
+ this.batchTimer = null;
691
+ }
572
692
  getDesiredBatchFlushDueAt(nowTs) {
573
693
  const start = this.initTs || nowTs;
574
694
  const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
@@ -579,7 +699,7 @@
579
699
  }
580
700
  return nowTs + this.batchIntervalMs;
581
701
  }
582
- info(message, data = {}) {
702
+ info(message, data = {}, options) {
583
703
  let msg = '';
584
704
  let extra = {};
585
705
  if (message instanceof Error) {
@@ -595,9 +715,18 @@
595
715
  extra = data;
596
716
  }
597
717
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
598
- this.report({ type: 'info', data: payload, timestamp: Date.now() });
718
+ const log = { type: 'info', data: payload, timestamp: Date.now() };
719
+ // info 默认走批量队列,支持 immediate 选项立即发送
720
+ if (options?.immediate) {
721
+ void this.sendReportLogs([log]).catch(() => {
722
+ this.cacheFailedReportLogs([log]);
723
+ });
724
+ }
725
+ else {
726
+ this.report(log);
727
+ }
599
728
  }
600
- warn(message, data = {}) {
729
+ warn(message, data = {}, options) {
601
730
  let msg = '';
602
731
  let extra = {};
603
732
  if (message instanceof Error) {
@@ -613,9 +742,18 @@
613
742
  extra = data;
614
743
  }
615
744
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
616
- this.report({ type: 'warn', data: payload, timestamp: Date.now() });
745
+ const log = { type: 'warn', data: payload, timestamp: Date.now() };
746
+ // warn 默认走批量队列,支持 immediate 选项立即发送
747
+ if (options?.immediate) {
748
+ void this.sendReportLogs([log]).catch(() => {
749
+ this.cacheFailedReportLogs([log]);
750
+ });
751
+ }
752
+ else {
753
+ this.report(log);
754
+ }
617
755
  }
618
- error(message, data = {}) {
756
+ error(message, data = {}, options) {
619
757
  let msg = '';
620
758
  let extra = {};
621
759
  if (message instanceof Error) {
@@ -631,7 +769,17 @@
631
769
  extra = data;
632
770
  }
633
771
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
634
- this.report({ type: 'error', data: payload, timestamp: Date.now() });
772
+ const log = { type: 'error', data: payload, timestamp: Date.now() };
773
+ // error 默认即时上报,除非显式指定 immediate: false
774
+ const immediate = options?.immediate ?? true;
775
+ if (immediate) {
776
+ void this.sendReportLogs([log]).catch(() => {
777
+ this.cacheFailedReportLogs([log]);
778
+ });
779
+ }
780
+ else {
781
+ this.report(log);
782
+ }
635
783
  }
636
784
  track(trackType, data = {}) {
637
785
  if (!trackType)
@@ -646,10 +794,7 @@
646
794
  * 立即发送内存队列
647
795
  */
648
796
  async flushBatch() {
649
- if (this.batchTimer) {
650
- clearTimeout(this.batchTimer);
651
- this.batchTimer = null;
652
- }
797
+ this.cancelScheduledFlush();
653
798
  this.batchTimerDueAt = null;
654
799
  if (this.memoryQueue.length === 0)
655
800
  return;
@@ -1294,6 +1439,13 @@
1294
1439
  const DEFAULT_MAX_TEXT$1 = 4000;
1295
1440
  const DEFAULT_DEDUPE_WINDOW_MS$1 = 3000;
1296
1441
  const DEFAULT_DEDUPE_MAX_KEYS$1 = 200;
1442
+ /** 默认忽略的无意义错误信息 */
1443
+ const DEFAULT_IGNORE_MESSAGES$1 = [
1444
+ 'Script error.',
1445
+ 'Script error',
1446
+ /^ResizeObserver loop/,
1447
+ 'Permission was denied',
1448
+ ];
1297
1449
  function truncate$3(s, maxLen) {
1298
1450
  if (!s)
1299
1451
  return s;
@@ -1306,6 +1458,17 @@
1306
1458
  return false;
1307
1459
  return Math.random() < sampleRate;
1308
1460
  }
1461
+ /** 检查消息是否应该被忽略 */
1462
+ function shouldIgnoreMessage$1(message, ignorePatterns) {
1463
+ if (!message || ignorePatterns.length === 0)
1464
+ return false;
1465
+ return ignorePatterns.some((pattern) => {
1466
+ if (typeof pattern === 'string') {
1467
+ return message === pattern || message.includes(pattern);
1468
+ }
1469
+ return pattern.test(message);
1470
+ });
1471
+ }
1309
1472
  function getPagePath$1() {
1310
1473
  try {
1311
1474
  if (typeof window === 'undefined')
@@ -1401,6 +1564,9 @@
1401
1564
  };
1402
1565
  if (event && typeof event === 'object' && 'message' in event) {
1403
1566
  payload.message = truncate$3(String(event.message ?? ''), options.maxTextLength);
1567
+ // 检查是否应该忽略此错误
1568
+ if (shouldIgnoreMessage$1(String(event.message ?? ''), options.ignoreMessages))
1569
+ return;
1404
1570
  payload.filename = truncate$3(String(event.filename ?? ''), 500);
1405
1571
  payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
1406
1572
  payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
@@ -1439,6 +1605,9 @@
1439
1605
  return;
1440
1606
  const reason = event?.reason;
1441
1607
  const e = normalizeErrorLike$1(reason, options.maxTextLength);
1608
+ // 检查是否应该忽略此错误
1609
+ if (shouldIgnoreMessage$1(e.message, options.ignoreMessages))
1610
+ return;
1442
1611
  const payload = {
1443
1612
  pagePath: getPagePath$1(),
1444
1613
  source: 'unhandledrejection',
@@ -1468,6 +1637,7 @@
1468
1637
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT$1,
1469
1638
  dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS$1,
1470
1639
  dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS$1,
1640
+ ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES$1,
1471
1641
  };
1472
1642
  installBrowserErrorMonitor(report, options);
1473
1643
  }
@@ -1475,6 +1645,13 @@
1475
1645
  const DEFAULT_MAX_TEXT = 4000;
1476
1646
  const DEFAULT_DEDUPE_WINDOW_MS = 3000;
1477
1647
  const DEFAULT_DEDUPE_MAX_KEYS = 200;
1648
+ /** 默认忽略的无意义错误信息 */
1649
+ const DEFAULT_IGNORE_MESSAGES = [
1650
+ 'Script error.',
1651
+ 'Script error',
1652
+ /^ResizeObserver loop/,
1653
+ 'Permission was denied',
1654
+ ];
1478
1655
  function truncate$2(s, maxLen) {
1479
1656
  if (!s)
1480
1657
  return s;
@@ -1487,6 +1664,17 @@
1487
1664
  return false;
1488
1665
  return Math.random() < sampleRate;
1489
1666
  }
1667
+ /** 检查消息是否应该被忽略 */
1668
+ function shouldIgnoreMessage(message, ignorePatterns) {
1669
+ if (!message || ignorePatterns.length === 0)
1670
+ return false;
1671
+ return ignorePatterns.some((pattern) => {
1672
+ if (typeof pattern === 'string') {
1673
+ return message === pattern || message.includes(pattern);
1674
+ }
1675
+ return pattern.test(message);
1676
+ });
1677
+ }
1490
1678
  function getMpPagePath() {
1491
1679
  try {
1492
1680
  const pages = globalThis.getCurrentPages?.();
@@ -1581,6 +1769,9 @@
1581
1769
  if (!sampleHit$2(options.sampleRate))
1582
1770
  return;
1583
1771
  const e = normalizeErrorLike(msg, options.maxTextLength);
1772
+ // 检查是否应该忽略此错误
1773
+ if (shouldIgnoreMessage(e.message, options.ignoreMessages))
1774
+ return;
1584
1775
  const payload = {
1585
1776
  pagePath: getMpPagePath(),
1586
1777
  source: 'wx.onError',
@@ -1608,6 +1799,9 @@
1608
1799
  if (!sampleHit$2(options.sampleRate))
1609
1800
  return;
1610
1801
  const e = normalizeErrorLike(res?.reason, options.maxTextLength);
1802
+ // 检查是否应该忽略此错误
1803
+ if (shouldIgnoreMessage(e.message, options.ignoreMessages))
1804
+ return;
1611
1805
  const payload = {
1612
1806
  pagePath: getMpPagePath(),
1613
1807
  source: 'wx.onUnhandledRejection',
@@ -1639,15 +1833,18 @@
1639
1833
  try {
1640
1834
  if (sampleHit$2(options.sampleRate)) {
1641
1835
  const e = normalizeErrorLike(args?.[0], options.maxTextLength);
1642
- const payload = {
1643
- pagePath: getMpPagePath(),
1644
- source: 'App.onError',
1645
- message: e.message,
1646
- errorName: e.name,
1647
- stack: e.stack,
1648
- };
1649
- if (shouldReport(buildErrorKey(options.reportType, payload)))
1650
- report(options.reportType, payload);
1836
+ // 检查是否应该忽略此错误
1837
+ if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
1838
+ const payload = {
1839
+ pagePath: getMpPagePath(),
1840
+ source: 'App.onError',
1841
+ message: e.message,
1842
+ errorName: e.name,
1843
+ stack: e.stack,
1844
+ };
1845
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
1846
+ report(options.reportType, payload);
1847
+ }
1651
1848
  }
1652
1849
  }
1653
1850
  catch {
@@ -1663,15 +1860,18 @@
1663
1860
  if (sampleHit$2(options.sampleRate)) {
1664
1861
  const reason = args?.[0]?.reason ?? args?.[0];
1665
1862
  const e = normalizeErrorLike(reason, options.maxTextLength);
1666
- const payload = {
1667
- pagePath: getMpPagePath(),
1668
- source: 'App.onUnhandledRejection',
1669
- message: e.message,
1670
- errorName: e.name,
1671
- stack: e.stack,
1672
- };
1673
- if (shouldReport(buildErrorKey(options.reportType, payload)))
1674
- report(options.reportType, payload);
1863
+ // 检查是否应该忽略此错误
1864
+ if (!shouldIgnoreMessage(e.message, options.ignoreMessages)) {
1865
+ const payload = {
1866
+ pagePath: getMpPagePath(),
1867
+ source: 'App.onUnhandledRejection',
1868
+ message: e.message,
1869
+ errorName: e.name,
1870
+ stack: e.stack,
1871
+ };
1872
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
1873
+ report(options.reportType, payload);
1874
+ }
1675
1875
  }
1676
1876
  }
1677
1877
  catch {
@@ -1702,6 +1902,7 @@
1702
1902
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
1703
1903
  dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
1704
1904
  dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
1905
+ ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
1705
1906
  };
1706
1907
  installMiniProgramErrorMonitor(report, options);
1707
1908
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errorMonitor.d.ts","sourceRoot":"","sources":["../../src/mini/errorMonitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGhE,KAAK,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AAoNzD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,OAAO,GAAG,mBAAmB,GAAG,SAAc,GAAG,IAAI,CAgBpH"}
1
+ {"version":3,"file":"errorMonitor.d.ts","sourceRoot":"","sources":["../../src/mini/errorMonitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGhE,KAAK,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AAuPzD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,OAAO,GAAG,mBAAmB,GAAG,SAAc,GAAG,IAAI,CAiBpH"}