@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/web.esm.js CHANGED
@@ -178,6 +178,9 @@ class ClsLoggerCore {
178
178
  this.batchTimerDueAt = null;
179
179
  this.initTs = 0;
180
180
  this.startupDelayMs = 0;
181
+ this.useIdleCallback = false;
182
+ this.idleTimeout = 3000;
183
+ this.visibilityCleanup = null;
181
184
  // 参考文档:失败缓存 + 重试
182
185
  this.failedCacheKey = 'cls_failed_logs';
183
186
  this.failedCacheMax = 200;
@@ -247,6 +250,8 @@ class ClsLoggerCore {
247
250
  this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
248
251
  this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
249
252
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
253
+ this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
254
+ this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
250
255
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
251
256
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
252
257
  // 预热(避免首条日志触发 import/初始化开销)
@@ -256,6 +261,8 @@ class ClsLoggerCore {
256
261
  if (this.enabled) {
257
262
  // 启动时尝试发送失败缓存
258
263
  this.flushFailed();
264
+ // 添加页面可见性监听(确保页面关闭时数据不丢失)
265
+ this.setupVisibilityListener();
259
266
  // 初始化后立即启动请求监听
260
267
  this.startRequestMonitor(options.requestMonitor);
261
268
  // 初始化后立即启动错误监控/性能监控
@@ -292,6 +299,89 @@ class ClsLoggerCore {
292
299
  return auto;
293
300
  return undefined;
294
301
  }
302
+ /**
303
+ * 设置页面可见性监听
304
+ * - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
305
+ * - pagehide: 作为移动端 fallback
306
+ */
307
+ setupVisibilityListener() {
308
+ if (typeof document === 'undefined' || typeof window === 'undefined')
309
+ return;
310
+ // 避免重复监听
311
+ if (this.visibilityCleanup)
312
+ return;
313
+ const handleVisibilityChange = () => {
314
+ if (document.visibilityState === 'hidden') {
315
+ this.flushBatchSync();
316
+ }
317
+ };
318
+ const handlePageHide = () => {
319
+ this.flushBatchSync();
320
+ };
321
+ document.addEventListener('visibilitychange', handleVisibilityChange);
322
+ window.addEventListener('pagehide', handlePageHide);
323
+ this.visibilityCleanup = () => {
324
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
325
+ window.removeEventListener('pagehide', handlePageHide);
326
+ };
327
+ }
328
+ /**
329
+ * 同步发送内存队列(使用 sendBeacon)
330
+ * - 用于页面关闭时确保数据发送
331
+ * - sendBeacon 不可用时降级为缓存到 localStorage
332
+ */
333
+ flushBatchSync() {
334
+ if (this.memoryQueue.length === 0)
335
+ return;
336
+ // 清除定时器
337
+ if (this.batchTimer) {
338
+ try {
339
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
340
+ cancelIdleCallback(this.batchTimer);
341
+ }
342
+ else {
343
+ clearTimeout(this.batchTimer);
344
+ }
345
+ }
346
+ catch {
347
+ // ignore
348
+ }
349
+ this.batchTimer = null;
350
+ }
351
+ this.batchTimerDueAt = null;
352
+ const logs = [...this.memoryQueue];
353
+ this.memoryQueue = [];
354
+ // 优先使用 sendBeacon(页面关闭时可靠发送)
355
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
356
+ try {
357
+ const payload = this.buildSendBeaconPayload(logs);
358
+ const blob = new Blob([payload], { type: 'application/json' });
359
+ const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
360
+ const success = navigator.sendBeacon(url, blob);
361
+ if (!success) {
362
+ // sendBeacon 返回 false 时,降级缓存
363
+ this.cacheFailedReportLogs(logs);
364
+ }
365
+ }
366
+ catch {
367
+ this.cacheFailedReportLogs(logs);
368
+ }
369
+ }
370
+ else {
371
+ // 不支持 sendBeacon,降级缓存到 localStorage
372
+ this.cacheFailedReportLogs(logs);
373
+ }
374
+ }
375
+ /**
376
+ * 构建 sendBeacon 的 payload
377
+ */
378
+ buildSendBeaconPayload(logs) {
379
+ const logList = logs.map((log) => this.buildReportFields(log));
380
+ return JSON.stringify({
381
+ source: this.source,
382
+ logs: logList,
383
+ });
384
+ }
295
385
  startRequestMonitor(requestMonitor) {
296
386
  if (this.requestMonitorStarted)
297
387
  return;
@@ -546,25 +636,55 @@ class ClsLoggerCore {
546
636
  const desiredDelay = Math.max(0, desiredDueAt - now);
547
637
  if (!this.batchTimer) {
548
638
  this.batchTimerDueAt = desiredDueAt;
549
- this.batchTimer = setTimeout(() => {
550
- void this.flushBatch();
551
- }, desiredDelay);
639
+ this.scheduleFlush(desiredDelay);
552
640
  return;
553
641
  }
554
- // 启动合并窗口内:如果当前 timer 会“更早”触发,则延后到窗口结束,尽量减少多次发送
642
+ // 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
555
643
  if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
556
- try {
557
- clearTimeout(this.batchTimer);
558
- }
559
- catch {
560
- // ignore
561
- }
644
+ this.cancelScheduledFlush();
562
645
  this.batchTimerDueAt = desiredDueAt;
646
+ this.scheduleFlush(desiredDelay);
647
+ }
648
+ }
649
+ /**
650
+ * 调度批量发送
651
+ * - 支持 requestIdleCallback(浏览器空闲时执行)
652
+ * - 降级为 setTimeout
653
+ */
654
+ scheduleFlush(desiredDelay) {
655
+ if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
656
+ // 使用 requestIdleCallback,设置 timeout 保证最终执行
657
+ const idleId = requestIdleCallback(() => {
658
+ void this.flushBatch();
659
+ }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
660
+ // 存储 idleId 以便清理(类型兼容处理)
661
+ this.batchTimer = idleId;
662
+ }
663
+ else {
563
664
  this.batchTimer = setTimeout(() => {
564
665
  void this.flushBatch();
565
666
  }, desiredDelay);
566
667
  }
567
668
  }
669
+ /**
670
+ * 取消已调度的批量发送
671
+ */
672
+ cancelScheduledFlush() {
673
+ if (!this.batchTimer)
674
+ return;
675
+ try {
676
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
677
+ cancelIdleCallback(this.batchTimer);
678
+ }
679
+ else {
680
+ clearTimeout(this.batchTimer);
681
+ }
682
+ }
683
+ catch {
684
+ // ignore
685
+ }
686
+ this.batchTimer = null;
687
+ }
568
688
  getDesiredBatchFlushDueAt(nowTs) {
569
689
  const start = this.initTs || nowTs;
570
690
  const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
@@ -575,7 +695,7 @@ class ClsLoggerCore {
575
695
  }
576
696
  return nowTs + this.batchIntervalMs;
577
697
  }
578
- info(message, data = {}) {
698
+ info(message, data = {}, options) {
579
699
  let msg = '';
580
700
  let extra = {};
581
701
  if (message instanceof Error) {
@@ -591,9 +711,18 @@ class ClsLoggerCore {
591
711
  extra = data;
592
712
  }
593
713
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
594
- this.report({ type: 'info', data: payload, timestamp: Date.now() });
714
+ const log = { type: 'info', data: payload, timestamp: Date.now() };
715
+ // info 默认走批量队列,支持 immediate 选项立即发送
716
+ if (options?.immediate) {
717
+ void this.sendReportLogs([log]).catch(() => {
718
+ this.cacheFailedReportLogs([log]);
719
+ });
720
+ }
721
+ else {
722
+ this.report(log);
723
+ }
595
724
  }
596
- warn(message, data = {}) {
725
+ warn(message, data = {}, options) {
597
726
  let msg = '';
598
727
  let extra = {};
599
728
  if (message instanceof Error) {
@@ -609,9 +738,18 @@ class ClsLoggerCore {
609
738
  extra = data;
610
739
  }
611
740
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
612
- this.report({ type: 'warn', data: payload, timestamp: Date.now() });
741
+ const log = { type: 'warn', data: payload, timestamp: Date.now() };
742
+ // warn 默认走批量队列,支持 immediate 选项立即发送
743
+ if (options?.immediate) {
744
+ void this.sendReportLogs([log]).catch(() => {
745
+ this.cacheFailedReportLogs([log]);
746
+ });
747
+ }
748
+ else {
749
+ this.report(log);
750
+ }
613
751
  }
614
- error(message, data = {}) {
752
+ error(message, data = {}, options) {
615
753
  let msg = '';
616
754
  let extra = {};
617
755
  if (message instanceof Error) {
@@ -627,7 +765,17 @@ class ClsLoggerCore {
627
765
  extra = data;
628
766
  }
629
767
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
630
- this.report({ type: 'error', data: payload, timestamp: Date.now() });
768
+ const log = { type: 'error', data: payload, timestamp: Date.now() };
769
+ // error 默认即时上报,除非显式指定 immediate: false
770
+ const immediate = options?.immediate ?? true;
771
+ if (immediate) {
772
+ void this.sendReportLogs([log]).catch(() => {
773
+ this.cacheFailedReportLogs([log]);
774
+ });
775
+ }
776
+ else {
777
+ this.report(log);
778
+ }
631
779
  }
632
780
  track(trackType, data = {}) {
633
781
  if (!trackType)
@@ -642,10 +790,7 @@ class ClsLoggerCore {
642
790
  * 立即发送内存队列
643
791
  */
644
792
  async flushBatch() {
645
- if (this.batchTimer) {
646
- clearTimeout(this.batchTimer);
647
- this.batchTimer = null;
648
- }
793
+ this.cancelScheduledFlush();
649
794
  this.batchTimerDueAt = null;
650
795
  if (this.memoryQueue.length === 0)
651
796
  return;
@@ -1045,6 +1190,13 @@ function installWebRequestMonitor(report, opts = {}) {
1045
1190
  const DEFAULT_MAX_TEXT = 4000;
1046
1191
  const DEFAULT_DEDUPE_WINDOW_MS = 3000;
1047
1192
  const DEFAULT_DEDUPE_MAX_KEYS = 200;
1193
+ /** 默认忽略的无意义错误信息 */
1194
+ const DEFAULT_IGNORE_MESSAGES = [
1195
+ 'Script error.',
1196
+ 'Script error',
1197
+ /^ResizeObserver loop/,
1198
+ 'Permission was denied',
1199
+ ];
1048
1200
  function truncate$2(s, maxLen) {
1049
1201
  if (!s)
1050
1202
  return s;
@@ -1057,6 +1209,17 @@ function sampleHit$1(sampleRate) {
1057
1209
  return false;
1058
1210
  return Math.random() < sampleRate;
1059
1211
  }
1212
+ /** 检查消息是否应该被忽略 */
1213
+ function shouldIgnoreMessage(message, ignorePatterns) {
1214
+ if (!message || ignorePatterns.length === 0)
1215
+ return false;
1216
+ return ignorePatterns.some((pattern) => {
1217
+ if (typeof pattern === 'string') {
1218
+ return message === pattern || message.includes(pattern);
1219
+ }
1220
+ return pattern.test(message);
1221
+ });
1222
+ }
1060
1223
  function getPagePath$1() {
1061
1224
  try {
1062
1225
  if (typeof window === 'undefined')
@@ -1152,6 +1315,9 @@ function installBrowserErrorMonitor(report, options) {
1152
1315
  };
1153
1316
  if (event && typeof event === 'object' && 'message' in event) {
1154
1317
  payload.message = truncate$2(String(event.message ?? ''), options.maxTextLength);
1318
+ // 检查是否应该忽略此错误
1319
+ if (shouldIgnoreMessage(String(event.message ?? ''), options.ignoreMessages))
1320
+ return;
1155
1321
  payload.filename = truncate$2(String(event.filename ?? ''), 500);
1156
1322
  payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
1157
1323
  payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
@@ -1190,6 +1356,9 @@ function installBrowserErrorMonitor(report, options) {
1190
1356
  return;
1191
1357
  const reason = event?.reason;
1192
1358
  const e = normalizeErrorLike(reason, options.maxTextLength);
1359
+ // 检查是否应该忽略此错误
1360
+ if (shouldIgnoreMessage(e.message, options.ignoreMessages))
1361
+ return;
1193
1362
  const payload = {
1194
1363
  pagePath: getPagePath$1(),
1195
1364
  source: 'unhandledrejection',
@@ -1219,6 +1388,7 @@ function installWebErrorMonitor(report, opts = {}) {
1219
1388
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
1220
1389
  dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
1221
1390
  dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
1391
+ ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
1222
1392
  };
1223
1393
  installBrowserErrorMonitor(report, options);
1224
1394
  }
package/dist/web.js CHANGED
@@ -201,6 +201,9 @@ class ClsLoggerCore {
201
201
  this.batchTimerDueAt = null;
202
202
  this.initTs = 0;
203
203
  this.startupDelayMs = 0;
204
+ this.useIdleCallback = false;
205
+ this.idleTimeout = 3000;
206
+ this.visibilityCleanup = null;
204
207
  // 参考文档:失败缓存 + 重试
205
208
  this.failedCacheKey = 'cls_failed_logs';
206
209
  this.failedCacheMax = 200;
@@ -270,6 +273,8 @@ class ClsLoggerCore {
270
273
  this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
271
274
  this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
272
275
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
276
+ this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
277
+ this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
273
278
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
274
279
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
275
280
  // 预热(避免首条日志触发 import/初始化开销)
@@ -279,6 +284,8 @@ class ClsLoggerCore {
279
284
  if (this.enabled) {
280
285
  // 启动时尝试发送失败缓存
281
286
  this.flushFailed();
287
+ // 添加页面可见性监听(确保页面关闭时数据不丢失)
288
+ this.setupVisibilityListener();
282
289
  // 初始化后立即启动请求监听
283
290
  this.startRequestMonitor(options.requestMonitor);
284
291
  // 初始化后立即启动错误监控/性能监控
@@ -315,6 +322,89 @@ class ClsLoggerCore {
315
322
  return auto;
316
323
  return undefined;
317
324
  }
325
+ /**
326
+ * 设置页面可见性监听
327
+ * - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
328
+ * - pagehide: 作为移动端 fallback
329
+ */
330
+ setupVisibilityListener() {
331
+ if (typeof document === 'undefined' || typeof window === 'undefined')
332
+ return;
333
+ // 避免重复监听
334
+ if (this.visibilityCleanup)
335
+ return;
336
+ const handleVisibilityChange = () => {
337
+ if (document.visibilityState === 'hidden') {
338
+ this.flushBatchSync();
339
+ }
340
+ };
341
+ const handlePageHide = () => {
342
+ this.flushBatchSync();
343
+ };
344
+ document.addEventListener('visibilitychange', handleVisibilityChange);
345
+ window.addEventListener('pagehide', handlePageHide);
346
+ this.visibilityCleanup = () => {
347
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
348
+ window.removeEventListener('pagehide', handlePageHide);
349
+ };
350
+ }
351
+ /**
352
+ * 同步发送内存队列(使用 sendBeacon)
353
+ * - 用于页面关闭时确保数据发送
354
+ * - sendBeacon 不可用时降级为缓存到 localStorage
355
+ */
356
+ flushBatchSync() {
357
+ if (this.memoryQueue.length === 0)
358
+ return;
359
+ // 清除定时器
360
+ if (this.batchTimer) {
361
+ try {
362
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
363
+ cancelIdleCallback(this.batchTimer);
364
+ }
365
+ else {
366
+ clearTimeout(this.batchTimer);
367
+ }
368
+ }
369
+ catch {
370
+ // ignore
371
+ }
372
+ this.batchTimer = null;
373
+ }
374
+ this.batchTimerDueAt = null;
375
+ const logs = [...this.memoryQueue];
376
+ this.memoryQueue = [];
377
+ // 优先使用 sendBeacon(页面关闭时可靠发送)
378
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
379
+ try {
380
+ const payload = this.buildSendBeaconPayload(logs);
381
+ const blob = new Blob([payload], { type: 'application/json' });
382
+ const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
383
+ const success = navigator.sendBeacon(url, blob);
384
+ if (!success) {
385
+ // sendBeacon 返回 false 时,降级缓存
386
+ this.cacheFailedReportLogs(logs);
387
+ }
388
+ }
389
+ catch {
390
+ this.cacheFailedReportLogs(logs);
391
+ }
392
+ }
393
+ else {
394
+ // 不支持 sendBeacon,降级缓存到 localStorage
395
+ this.cacheFailedReportLogs(logs);
396
+ }
397
+ }
398
+ /**
399
+ * 构建 sendBeacon 的 payload
400
+ */
401
+ buildSendBeaconPayload(logs) {
402
+ const logList = logs.map((log) => this.buildReportFields(log));
403
+ return JSON.stringify({
404
+ source: this.source,
405
+ logs: logList,
406
+ });
407
+ }
318
408
  startRequestMonitor(requestMonitor) {
319
409
  if (this.requestMonitorStarted)
320
410
  return;
@@ -569,25 +659,55 @@ class ClsLoggerCore {
569
659
  const desiredDelay = Math.max(0, desiredDueAt - now);
570
660
  if (!this.batchTimer) {
571
661
  this.batchTimerDueAt = desiredDueAt;
572
- this.batchTimer = setTimeout(() => {
573
- void this.flushBatch();
574
- }, desiredDelay);
662
+ this.scheduleFlush(desiredDelay);
575
663
  return;
576
664
  }
577
- // 启动合并窗口内:如果当前 timer 会“更早”触发,则延后到窗口结束,尽量减少多次发送
665
+ // 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
578
666
  if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
579
- try {
580
- clearTimeout(this.batchTimer);
581
- }
582
- catch {
583
- // ignore
584
- }
667
+ this.cancelScheduledFlush();
585
668
  this.batchTimerDueAt = desiredDueAt;
669
+ this.scheduleFlush(desiredDelay);
670
+ }
671
+ }
672
+ /**
673
+ * 调度批量发送
674
+ * - 支持 requestIdleCallback(浏览器空闲时执行)
675
+ * - 降级为 setTimeout
676
+ */
677
+ scheduleFlush(desiredDelay) {
678
+ if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
679
+ // 使用 requestIdleCallback,设置 timeout 保证最终执行
680
+ const idleId = requestIdleCallback(() => {
681
+ void this.flushBatch();
682
+ }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
683
+ // 存储 idleId 以便清理(类型兼容处理)
684
+ this.batchTimer = idleId;
685
+ }
686
+ else {
586
687
  this.batchTimer = setTimeout(() => {
587
688
  void this.flushBatch();
588
689
  }, desiredDelay);
589
690
  }
590
691
  }
692
+ /**
693
+ * 取消已调度的批量发送
694
+ */
695
+ cancelScheduledFlush() {
696
+ if (!this.batchTimer)
697
+ return;
698
+ try {
699
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
700
+ cancelIdleCallback(this.batchTimer);
701
+ }
702
+ else {
703
+ clearTimeout(this.batchTimer);
704
+ }
705
+ }
706
+ catch {
707
+ // ignore
708
+ }
709
+ this.batchTimer = null;
710
+ }
591
711
  getDesiredBatchFlushDueAt(nowTs) {
592
712
  const start = this.initTs || nowTs;
593
713
  const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
@@ -598,7 +718,7 @@ class ClsLoggerCore {
598
718
  }
599
719
  return nowTs + this.batchIntervalMs;
600
720
  }
601
- info(message, data = {}) {
721
+ info(message, data = {}, options) {
602
722
  let msg = '';
603
723
  let extra = {};
604
724
  if (message instanceof Error) {
@@ -614,9 +734,18 @@ class ClsLoggerCore {
614
734
  extra = data;
615
735
  }
616
736
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
617
- this.report({ type: 'info', data: payload, timestamp: Date.now() });
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
+ }
618
747
  }
619
- warn(message, data = {}) {
748
+ warn(message, data = {}, options) {
620
749
  let msg = '';
621
750
  let extra = {};
622
751
  if (message instanceof Error) {
@@ -632,9 +761,18 @@ class ClsLoggerCore {
632
761
  extra = data;
633
762
  }
634
763
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
635
- this.report({ type: 'warn', data: payload, timestamp: Date.now() });
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
+ }
636
774
  }
637
- error(message, data = {}) {
775
+ error(message, data = {}, options) {
638
776
  let msg = '';
639
777
  let extra = {};
640
778
  if (message instanceof Error) {
@@ -650,7 +788,17 @@ class ClsLoggerCore {
650
788
  extra = data;
651
789
  }
652
790
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
653
- this.report({ type: 'error', data: payload, timestamp: Date.now() });
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
+ }
654
802
  }
655
803
  track(trackType, data = {}) {
656
804
  if (!trackType)
@@ -665,10 +813,7 @@ class ClsLoggerCore {
665
813
  * 立即发送内存队列
666
814
  */
667
815
  async flushBatch() {
668
- if (this.batchTimer) {
669
- clearTimeout(this.batchTimer);
670
- this.batchTimer = null;
671
- }
816
+ this.cancelScheduledFlush();
672
817
  this.batchTimerDueAt = null;
673
818
  if (this.memoryQueue.length === 0)
674
819
  return;
@@ -1068,6 +1213,13 @@ function installWebRequestMonitor(report, opts = {}) {
1068
1213
  const DEFAULT_MAX_TEXT = 4000;
1069
1214
  const DEFAULT_DEDUPE_WINDOW_MS = 3000;
1070
1215
  const DEFAULT_DEDUPE_MAX_KEYS = 200;
1216
+ /** 默认忽略的无意义错误信息 */
1217
+ const DEFAULT_IGNORE_MESSAGES = [
1218
+ 'Script error.',
1219
+ 'Script error',
1220
+ /^ResizeObserver loop/,
1221
+ 'Permission was denied',
1222
+ ];
1071
1223
  function truncate$2(s, maxLen) {
1072
1224
  if (!s)
1073
1225
  return s;
@@ -1080,6 +1232,17 @@ function sampleHit$1(sampleRate) {
1080
1232
  return false;
1081
1233
  return Math.random() < sampleRate;
1082
1234
  }
1235
+ /** 检查消息是否应该被忽略 */
1236
+ function shouldIgnoreMessage(message, ignorePatterns) {
1237
+ if (!message || ignorePatterns.length === 0)
1238
+ return false;
1239
+ return ignorePatterns.some((pattern) => {
1240
+ if (typeof pattern === 'string') {
1241
+ return message === pattern || message.includes(pattern);
1242
+ }
1243
+ return pattern.test(message);
1244
+ });
1245
+ }
1083
1246
  function getPagePath$1() {
1084
1247
  try {
1085
1248
  if (typeof window === 'undefined')
@@ -1175,6 +1338,9 @@ function installBrowserErrorMonitor(report, options) {
1175
1338
  };
1176
1339
  if (event && typeof event === 'object' && 'message' in event) {
1177
1340
  payload.message = truncate$2(String(event.message ?? ''), options.maxTextLength);
1341
+ // 检查是否应该忽略此错误
1342
+ if (shouldIgnoreMessage(String(event.message ?? ''), options.ignoreMessages))
1343
+ return;
1178
1344
  payload.filename = truncate$2(String(event.filename ?? ''), 500);
1179
1345
  payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
1180
1346
  payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
@@ -1213,6 +1379,9 @@ function installBrowserErrorMonitor(report, options) {
1213
1379
  return;
1214
1380
  const reason = event?.reason;
1215
1381
  const e = normalizeErrorLike(reason, options.maxTextLength);
1382
+ // 检查是否应该忽略此错误
1383
+ if (shouldIgnoreMessage(e.message, options.ignoreMessages))
1384
+ return;
1216
1385
  const payload = {
1217
1386
  pagePath: getPagePath$1(),
1218
1387
  source: 'unhandledrejection',
@@ -1242,6 +1411,7 @@ function installWebErrorMonitor(report, opts = {}) {
1242
1411
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
1243
1412
  dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
1244
1413
  dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
1414
+ ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
1245
1415
  };
1246
1416
  installBrowserErrorMonitor(report, options);
1247
1417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@be-link/cls-logger",
3
- "version": "1.0.1-beta.17",
3
+ "version": "1.0.1-beta.19",
4
4
  "description": "@be-link cls-logger - 腾讯云 CLS 日志上报封装",
5
5
  "homepage": "https://github.com/snowmountain-top/be-link",
6
6
  "author": "zhuiyi",