@be-link/cls-logger 1.0.1-beta.3 → 1.0.1-beta.5

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.js CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var tencentcloudClsSdkJsWeb = require('tencentcloud-cls-sdk-js-web');
6
-
7
5
  function isPlainObject(value) {
8
6
  return Object.prototype.toString.call(value) === '[object Object]';
9
7
  }
@@ -540,6 +538,8 @@ function installRequestMonitor(report, opts = {}) {
540
538
  }
541
539
 
542
540
  const DEFAULT_MAX_TEXT = 4000;
541
+ const DEFAULT_DEDUPE_WINDOW_MS = 3000;
542
+ const DEFAULT_DEDUPE_MAX_KEYS = 200;
543
543
  function truncate$2(s, maxLen) {
544
544
  if (!s)
545
545
  return s;
@@ -573,6 +573,54 @@ function normalizeErrorLike(err, maxTextLength) {
573
573
  const message = truncate$2(stringifyLogValue(err), maxTextLength);
574
574
  return { message, name: '', stack: '' };
575
575
  }
576
+ function createDedupeGuard(options) {
577
+ const cache = new Map(); // key -> lastReportAt
578
+ const maxKeys = Math.max(0, options.dedupeMaxKeys);
579
+ const windowMs = Math.max(0, options.dedupeWindowMs);
580
+ function touch(key, now) {
581
+ // refresh insertion order
582
+ if (cache.has(key))
583
+ cache.delete(key);
584
+ cache.set(key, now);
585
+ if (maxKeys > 0) {
586
+ while (cache.size > maxKeys) {
587
+ const first = cache.keys().next().value;
588
+ if (!first)
589
+ break;
590
+ cache.delete(first);
591
+ }
592
+ }
593
+ }
594
+ return (key) => {
595
+ if (!key)
596
+ return true;
597
+ if (windowMs <= 0 || maxKeys === 0)
598
+ return true;
599
+ const now = Date.now();
600
+ const last = cache.get(key);
601
+ if (typeof last === 'number' && now - last < windowMs)
602
+ return false;
603
+ touch(key, now);
604
+ return true;
605
+ };
606
+ }
607
+ function buildErrorKey(type, payload) {
608
+ // 使用最核心、最稳定的字段构造签名,避免把瞬态字段(如 time)带入导致失效
609
+ const parts = [
610
+ type,
611
+ String(payload.source ?? ''),
612
+ String(payload.pagePath ?? ''),
613
+ String(payload.message ?? ''),
614
+ String(payload.errorName ?? ''),
615
+ String(payload.stack ?? ''),
616
+ String(payload.filename ?? ''),
617
+ String(payload.lineno ?? ''),
618
+ String(payload.colno ?? ''),
619
+ String(payload.tagName ?? ''),
620
+ String(payload.resourceUrl ?? ''),
621
+ ];
622
+ return parts.join('|');
623
+ }
576
624
  function installBrowserErrorMonitor(report, options) {
577
625
  if (typeof window === 'undefined')
578
626
  return;
@@ -580,6 +628,7 @@ function installBrowserErrorMonitor(report, options) {
580
628
  if (w.__beLinkClsLoggerErrorInstalled__)
581
629
  return;
582
630
  w.__beLinkClsLoggerErrorInstalled__ = true;
631
+ const shouldReport = createDedupeGuard(options);
583
632
  window.addEventListener('error', (event) => {
584
633
  try {
585
634
  if (!sampleHit$1(options.sampleRate))
@@ -602,6 +651,8 @@ function installBrowserErrorMonitor(report, options) {
602
651
  if (e.stack)
603
652
  payload.stack = e.stack;
604
653
  }
654
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
655
+ return;
605
656
  report(options.reportType, payload);
606
657
  return;
607
658
  }
@@ -613,6 +664,8 @@ function installBrowserErrorMonitor(report, options) {
613
664
  payload.source = 'resource.error';
614
665
  payload.tagName = tagName;
615
666
  payload.resourceUrl = truncate$2(url, 2000);
667
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
668
+ return;
616
669
  report(options.reportType, payload);
617
670
  }
618
671
  }
@@ -633,6 +686,8 @@ function installBrowserErrorMonitor(report, options) {
633
686
  errorName: e.name,
634
687
  stack: e.stack,
635
688
  };
689
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
690
+ return;
636
691
  report(options.reportType, payload);
637
692
  }
638
693
  catch {
@@ -645,6 +700,7 @@ function installMiniProgramErrorMonitor(report, options) {
645
700
  if (g.__beLinkClsLoggerMpErrorInstalled__)
646
701
  return;
647
702
  g.__beLinkClsLoggerMpErrorInstalled__ = true;
703
+ const shouldReport = createDedupeGuard(options);
648
704
  const wxAny = globalThis.wx;
649
705
  // wx.* 事件(兼容在 App 已经创建后的场景)
650
706
  try {
@@ -653,10 +709,13 @@ function installMiniProgramErrorMonitor(report, options) {
653
709
  try {
654
710
  if (!sampleHit$1(options.sampleRate))
655
711
  return;
656
- report(options.reportType, {
712
+ const payload = {
657
713
  source: 'wx.onError',
658
714
  message: truncate$2(String(msg ?? ''), options.maxTextLength),
659
- });
715
+ };
716
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
717
+ return;
718
+ report(options.reportType, payload);
660
719
  }
661
720
  catch {
662
721
  // ignore
@@ -674,12 +733,15 @@ function installMiniProgramErrorMonitor(report, options) {
674
733
  if (!sampleHit$1(options.sampleRate))
675
734
  return;
676
735
  const e = normalizeErrorLike(res?.reason, options.maxTextLength);
677
- report(options.reportType, {
736
+ const payload = {
678
737
  source: 'wx.onUnhandledRejection',
679
738
  message: e.message,
680
739
  errorName: e.name,
681
740
  stack: e.stack,
682
- });
741
+ };
742
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
743
+ return;
744
+ report(options.reportType, payload);
683
745
  }
684
746
  catch {
685
747
  // ignore
@@ -701,10 +763,12 @@ function installMiniProgramErrorMonitor(report, options) {
701
763
  next.onError = function (...args) {
702
764
  try {
703
765
  if (sampleHit$1(options.sampleRate)) {
704
- report(options.reportType, {
766
+ const payload = {
705
767
  source: 'App.onError',
706
768
  message: truncate$2(String(args?.[0] ?? ''), options.maxTextLength),
707
- });
769
+ };
770
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
771
+ report(options.reportType, payload);
708
772
  }
709
773
  }
710
774
  catch {
@@ -720,12 +784,14 @@ function installMiniProgramErrorMonitor(report, options) {
720
784
  if (sampleHit$1(options.sampleRate)) {
721
785
  const reason = args?.[0]?.reason ?? args?.[0];
722
786
  const e = normalizeErrorLike(reason, options.maxTextLength);
723
- report(options.reportType, {
787
+ const payload = {
724
788
  source: 'App.onUnhandledRejection',
725
789
  message: e.message,
726
790
  errorName: e.name,
727
791
  stack: e.stack,
728
- });
792
+ };
793
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
794
+ report(options.reportType, payload);
729
795
  }
730
796
  }
731
797
  catch {
@@ -754,6 +820,8 @@ function installErrorMonitor(report, opts = {}) {
754
820
  sampleRate: raw.sampleRate ?? 1,
755
821
  captureResourceError: raw.captureResourceError ?? true,
756
822
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
823
+ dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
824
+ dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
757
825
  };
758
826
  if (isMiniProgramEnv()) {
759
827
  installMiniProgramErrorMonitor(report, options);
@@ -1830,6 +1898,27 @@ function createAutoDeviceInfoBaseFields(envType, opts) {
1830
1898
  };
1831
1899
  }
1832
1900
 
1901
+ function readGlobal(key) {
1902
+ try {
1903
+ const g = globalThis;
1904
+ return g[key] ?? null;
1905
+ }
1906
+ catch {
1907
+ return null;
1908
+ }
1909
+ }
1910
+ function tryRequire(moduleName) {
1911
+ try {
1912
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
1913
+ const req = (typeof require === 'function' ? require : null);
1914
+ if (!req)
1915
+ return null;
1916
+ return req(moduleName);
1917
+ }
1918
+ catch {
1919
+ return null;
1920
+ }
1921
+ }
1833
1922
  function enterClsSendingGuard() {
1834
1923
  const g = globalThis;
1835
1924
  const next = (g.__beLinkClsLoggerSendingCount__ ?? 0) + 1;
@@ -1841,7 +1930,10 @@ function enterClsSendingGuard() {
1841
1930
  }
1842
1931
  class ClsLogger {
1843
1932
  constructor() {
1933
+ this.sdk = null;
1934
+ this.sdkPromise = null;
1844
1935
  this.client = null;
1936
+ this.clientPromise = null;
1845
1937
  this.topicId = null;
1846
1938
  this.endpoint = 'ap-shanghai.cls.tencentcs.com';
1847
1939
  this.retryTimes = 10;
@@ -1883,6 +1975,19 @@ class ClsLogger {
1883
1975
  console.warn('ClsLogger.init 没有传 topicID/topic_id');
1884
1976
  return;
1885
1977
  }
1978
+ const nextEnvType = options.envType ?? this.detectEnvType();
1979
+ // envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
1980
+ const envChanged = nextEnvType !== this.envType;
1981
+ const endpointChanged = endpoint !== this.endpoint;
1982
+ const retryChanged = retryTimes !== this.retryTimes;
1983
+ if (envChanged || endpointChanged || retryChanged) {
1984
+ this.client = null;
1985
+ this.clientPromise = null;
1986
+ }
1987
+ if (envChanged) {
1988
+ this.sdk = null;
1989
+ this.sdkPromise = null;
1990
+ }
1886
1991
  this.topicId = topicId;
1887
1992
  this.endpoint = endpoint;
1888
1993
  this.retryTimes = retryTimes;
@@ -1893,7 +1998,7 @@ class ClsLogger {
1893
1998
  this.projectName = options.projectName ?? this.projectName;
1894
1999
  this.appId = options.appId ?? this.appId;
1895
2000
  this.appVersion = options.appVersion ?? this.appVersion;
1896
- this.envType = options.envType ?? this.detectEnvType();
2001
+ this.envType = nextEnvType;
1897
2002
  this.userGenerateBaseFields = options.generateBaseFields ?? this.userGenerateBaseFields;
1898
2003
  this.autoGenerateBaseFields = createAutoDeviceInfoBaseFields(this.envType, options.deviceInfo);
1899
2004
  this.storageKey = options.storageKey ?? this.storageKey;
@@ -1903,8 +2008,10 @@ class ClsLogger {
1903
2008
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
1904
2009
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
1905
2010
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
1906
- // 预热 client
1907
- this.getInstance();
2011
+ // 预热(避免首条日志触发 import/初始化开销)
2012
+ void this.getInstance().catch(() => {
2013
+ // ignore
2014
+ });
1908
2015
  // 启动时尝试发送失败缓存
1909
2016
  this.flushFailed();
1910
2017
  // 初始化后立即启动请求监听
@@ -2008,14 +2115,70 @@ class ClsLogger {
2008
2115
  this.behaviorMonitorCleanup = null;
2009
2116
  this.behaviorMonitorStarted = false;
2010
2117
  }
2011
- getInstance() {
2118
+ /**
2119
+ * 获取 CLS client(按环境懒加载 SDK)
2120
+ * - browser: 优先走 UMD 全局变量 `tencentcloudClsSdkJsWeb`
2121
+ * - miniprogram: 优先走 require(webpack/taro 可解析),否则 fallback import()
2122
+ */
2123
+ async getInstance() {
2012
2124
  if (this.client)
2013
2125
  return this.client;
2014
- this.client = new tencentcloudClsSdkJsWeb.AsyncClient({
2015
- endpoint: this.endpoint,
2016
- retry_times: this.retryTimes,
2126
+ if (this.clientPromise)
2127
+ return this.clientPromise;
2128
+ this.clientPromise = this.loadSdk()
2129
+ .then(({ AsyncClient }) => {
2130
+ const client = new AsyncClient({
2131
+ endpoint: this.endpoint,
2132
+ retry_times: this.retryTimes,
2133
+ });
2134
+ this.client = client;
2135
+ return client;
2136
+ })
2137
+ .catch((err) => {
2138
+ // 失败后允许下次重试
2139
+ this.clientPromise = null;
2140
+ throw err;
2141
+ });
2142
+ return this.clientPromise;
2143
+ }
2144
+ async loadSdk() {
2145
+ if (this.sdk)
2146
+ return this.sdk;
2147
+ if (this.sdkPromise)
2148
+ return this.sdkPromise;
2149
+ const isMini = this.envType === 'miniprogram';
2150
+ const moduleName = isMini ? 'tencentcloud-cls-sdk-js-mini' : 'tencentcloud-cls-sdk-js-web';
2151
+ // UMD(浏览器脚本)优先读全局变量
2152
+ if (!isMini) {
2153
+ const g = readGlobal('tencentcloudClsSdkJsWeb');
2154
+ if (g?.AsyncClient && g?.Log && g?.LogGroup && g?.PutLogsRequest) {
2155
+ this.sdk = g;
2156
+ return this.sdk;
2157
+ }
2158
+ }
2159
+ // 尽量同步 require(小程序/webpack 里最稳),失败再走 import()
2160
+ const reqMod = tryRequire(moduleName);
2161
+ if (reqMod?.AsyncClient && reqMod?.Log && reqMod?.LogGroup && reqMod?.PutLogsRequest) {
2162
+ this.sdk = reqMod;
2163
+ return this.sdk;
2164
+ }
2165
+ this.sdkPromise = import(moduleName)
2166
+ .then((m) => {
2167
+ const mod = (m?.default && m.default.AsyncClient ? m.default : m);
2168
+ const sdk = {
2169
+ AsyncClient: mod.AsyncClient,
2170
+ Log: mod.Log,
2171
+ LogGroup: mod.LogGroup,
2172
+ PutLogsRequest: mod.PutLogsRequest,
2173
+ };
2174
+ this.sdk = sdk;
2175
+ return sdk;
2176
+ })
2177
+ .catch((err) => {
2178
+ this.sdkPromise = null;
2179
+ throw err;
2017
2180
  });
2018
- return this.client;
2181
+ return this.sdkPromise;
2019
2182
  }
2020
2183
  detectEnvType() {
2021
2184
  const wxAny = globalThis.wx;
@@ -2047,22 +2210,33 @@ class ClsLogger {
2047
2210
  appVersion: this.appVersion || undefined,
2048
2211
  ...normalizedFields,
2049
2212
  });
2050
- const client = this.getInstance();
2051
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2213
+ // 同步 API:内部异步发送,避免把网络异常冒泡到业务(尤其小程序)
2214
+ void this.putAsync(finalFields).catch(() => {
2215
+ // ignore
2216
+ });
2217
+ }
2218
+ async putAsync(finalFields) {
2219
+ if (!this.topicId)
2220
+ return;
2221
+ const sdk = await this.loadSdk();
2222
+ const client = await this.getInstance();
2223
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2052
2224
  logGroup.setSource(this.source);
2053
- const log = new tencentcloudClsSdkJsWeb.Log(Date.now());
2225
+ const log = new sdk.Log(Date.now());
2054
2226
  for (const key of Object.keys(finalFields)) {
2055
2227
  log.addContent(key, stringifyLogValue(finalFields[key]));
2056
2228
  }
2057
2229
  logGroup.addLog(log);
2058
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2230
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2059
2231
  const exit = enterClsSendingGuard();
2232
+ let p;
2060
2233
  try {
2061
- client.PutLogs(request);
2234
+ p = client.PutLogs(request);
2062
2235
  }
2063
2236
  finally {
2064
2237
  exit();
2065
2238
  }
2239
+ await p;
2066
2240
  }
2067
2241
  /**
2068
2242
  * 直接上报:把 data 序列化后放入指定 key(默认 “日志内容”)
@@ -2121,11 +2295,19 @@ class ClsLogger {
2121
2295
  console.warn('ClsLogger.putBatch:未初始化 topic_id');
2122
2296
  return;
2123
2297
  }
2124
- const client = this.getInstance();
2125
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2298
+ void this.putBatchAsync(queue).catch(() => {
2299
+ // ignore
2300
+ });
2301
+ }
2302
+ async putBatchAsync(queue) {
2303
+ if (!this.topicId)
2304
+ return;
2305
+ const sdk = await this.loadSdk();
2306
+ const client = await this.getInstance();
2307
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2126
2308
  logGroup.setSource(this.source);
2127
2309
  for (const item of queue) {
2128
- const log = new tencentcloudClsSdkJsWeb.Log(item.time);
2310
+ const log = new sdk.Log(item.time);
2129
2311
  const data = item.data ?? {};
2130
2312
  for (const key of Object.keys(data)) {
2131
2313
  log.addContent(key, stringifyLogValue(data[key]));
@@ -2134,14 +2316,16 @@ class ClsLogger {
2134
2316
  }
2135
2317
  if (logGroup.getLogs().length === 0)
2136
2318
  return;
2137
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2319
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2138
2320
  const exit = enterClsSendingGuard();
2321
+ let p;
2139
2322
  try {
2140
- client.PutLogs(request);
2323
+ p = client.PutLogs(request);
2141
2324
  }
2142
2325
  finally {
2143
2326
  exit();
2144
2327
  }
2328
+ await p;
2145
2329
  }
2146
2330
  /**
2147
2331
  * 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
@@ -2254,12 +2438,13 @@ class ClsLogger {
2254
2438
  async sendReportLogs(logs) {
2255
2439
  if (!this.topicId)
2256
2440
  return;
2257
- const client = this.getInstance();
2258
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2441
+ const sdk = await this.loadSdk();
2442
+ const client = await this.getInstance();
2443
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2259
2444
  logGroup.setSource(this.source);
2260
2445
  for (const item of logs) {
2261
2446
  const fields = this.buildReportFields(item);
2262
- const log = new tencentcloudClsSdkJsWeb.Log(fields.timestamp);
2447
+ const log = new sdk.Log(fields.timestamp);
2263
2448
  for (const key of Object.keys(fields)) {
2264
2449
  if (key === 'timestamp')
2265
2450
  continue;
@@ -2267,7 +2452,7 @@ class ClsLogger {
2267
2452
  }
2268
2453
  logGroup.addLog(log);
2269
2454
  }
2270
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2455
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2271
2456
  // 只在“发起网络请求”的同步阶段打标记,避免 requestMonitor 监控 CLS 上报请求导致递归
2272
2457
  const exit = enterClsSendingGuard();
2273
2458
  let p;