@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.
@@ -1,7 +1,9 @@
1
- import { AsyncClient } from 'tencentcloud-cls-sdk-js-web';
2
1
  import type { ClsLoggerInitOptions, FlatFields, PutOptions, QueueItem, ReportLog } from './types';
3
2
  export declare class ClsLogger {
3
+ private sdk;
4
+ private sdkPromise;
4
5
  private client;
6
+ private clientPromise;
5
7
  private topicId;
6
8
  private endpoint;
7
9
  private retryTimes;
@@ -42,7 +44,15 @@ export declare class ClsLogger {
42
44
  * - 如需重启:可再次调用 init(或自行调用 init 后的默认启动逻辑)
43
45
  */
44
46
  stopBehaviorMonitor(): void;
45
- getInstance(): AsyncClient;
47
+ /**
48
+ * 获取 CLS client(按环境懒加载 SDK)
49
+ * - browser: 优先走 UMD 全局变量 `tencentcloudClsSdkJsWeb`
50
+ * - miniprogram: 优先走 require(webpack/taro 可解析),否则 fallback import()
51
+ */
52
+ getInstance(): Promise<{
53
+ PutLogs: (request: unknown) => Promise<unknown> | unknown;
54
+ }>;
55
+ private loadSdk;
46
56
  private detectEnvType;
47
57
  /**
48
58
  * 直接上报:埋点入参必须是一维(扁平)Object
@@ -50,6 +60,7 @@ export declare class ClsLogger {
50
60
  * - 最终会把 fields 展开成 CLS 的 content(key/value 都会转成 string)
51
61
  */
52
62
  put(fields: FlatFields, options?: PutOptions): void;
63
+ private putAsync;
53
64
  /**
54
65
  * 直接上报:把 data 序列化后放入指定 key(默认 “日志内容”)
55
66
  */
@@ -67,6 +78,7 @@ export declare class ClsLogger {
67
78
  * 批量上报(每条 item.data 展开为 log content)
68
79
  */
69
80
  putBatch(queue: QueueItem[]): void;
81
+ private putBatchAsync;
70
82
  /**
71
83
  * 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
72
84
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ClsLogger.d.ts","sourceRoot":"","sources":["../src/ClsLogger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClsLogger.d.ts","sourceRoot":"","sources":["../src/ClsLogger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAGpB,UAAU,EAEV,UAAU,EACV,SAAS,EACT,SAAS,EAEV,MAAM,SAAS,CAAC;AA4DjB,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,MAAM,CAA8E;IAC5F,OAAO,CAAC,aAAa,CAAuF;IAC5G,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,MAAM,CAAe;IAE7B,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,sBAAsB,CAAmC;IACjE,OAAO,CAAC,sBAAsB,CAAmC;IACjE,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,SAAS,CAAM;IAGvB,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,eAAe,CAAO;IAC9B,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,cAAc,CAAa;IAGnC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,sBAAsB,CAA6B;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAElB,IAAI,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI;IAoEzC,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,oBAAoB;IAuB5B;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAU3B;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;KAAE,CAAC;YAsB7E,OAAO;IA2CrB,OAAO,CAAC,aAAa;IAMrB;;;;OAIG;IACH,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,UAAe,GAAG,IAAI;YA0BzC,QAAQ;IAyBtB;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,SAAS,EAAE,OAAO,GAAE,UAAe,GAAG,IAAI;IAK7E;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,UAAe,GAAG,IAAI;IA6B3D;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI;YAapB,aAAa;IA6B3B;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI;IAyC5B,OAAO,CAAC,yBAAyB;IAUjC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI;IAKlD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI;IAKlD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI;IAKnD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI;IASrD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBjC,OAAO,CAAC,iBAAiB;YAmBX,cAAc;IAgC5B,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,qBAAqB;IAc7B,WAAW,IAAI,IAAI;IAkBnB;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI;CAcjG"}
@@ -1 +1 @@
1
- {"version":3,"file":"errorMonitor.d.ts","sourceRoot":"","sources":["../src/errorMonitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG/D,KAAK,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AA8MzD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,OAAO,GAAG,mBAAmB,GAAG,SAAc,GAAG,IAAI,CAmBhH"}
1
+ {"version":3,"file":"errorMonitor.d.ts","sourceRoot":"","sources":["../src/errorMonitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG/D,KAAK,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;AA4QzD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,OAAO,GAAG,mBAAmB,GAAG,SAAc,GAAG,IAAI,CAqBhH"}
package/dist/index.esm.js CHANGED
@@ -1,5 +1,3 @@
1
- import { AsyncClient, LogGroup, Log, PutLogsRequest } from 'tencentcloud-cls-sdk-js-web';
2
-
3
1
  function isPlainObject(value) {
4
2
  return Object.prototype.toString.call(value) === '[object Object]';
5
3
  }
@@ -536,6 +534,8 @@ function installRequestMonitor(report, opts = {}) {
536
534
  }
537
535
 
538
536
  const DEFAULT_MAX_TEXT = 4000;
537
+ const DEFAULT_DEDUPE_WINDOW_MS = 3000;
538
+ const DEFAULT_DEDUPE_MAX_KEYS = 200;
539
539
  function truncate$2(s, maxLen) {
540
540
  if (!s)
541
541
  return s;
@@ -569,6 +569,54 @@ function normalizeErrorLike(err, maxTextLength) {
569
569
  const message = truncate$2(stringifyLogValue(err), maxTextLength);
570
570
  return { message, name: '', stack: '' };
571
571
  }
572
+ function createDedupeGuard(options) {
573
+ const cache = new Map(); // key -> lastReportAt
574
+ const maxKeys = Math.max(0, options.dedupeMaxKeys);
575
+ const windowMs = Math.max(0, options.dedupeWindowMs);
576
+ function touch(key, now) {
577
+ // refresh insertion order
578
+ if (cache.has(key))
579
+ cache.delete(key);
580
+ cache.set(key, now);
581
+ if (maxKeys > 0) {
582
+ while (cache.size > maxKeys) {
583
+ const first = cache.keys().next().value;
584
+ if (!first)
585
+ break;
586
+ cache.delete(first);
587
+ }
588
+ }
589
+ }
590
+ return (key) => {
591
+ if (!key)
592
+ return true;
593
+ if (windowMs <= 0 || maxKeys === 0)
594
+ return true;
595
+ const now = Date.now();
596
+ const last = cache.get(key);
597
+ if (typeof last === 'number' && now - last < windowMs)
598
+ return false;
599
+ touch(key, now);
600
+ return true;
601
+ };
602
+ }
603
+ function buildErrorKey(type, payload) {
604
+ // 使用最核心、最稳定的字段构造签名,避免把瞬态字段(如 time)带入导致失效
605
+ const parts = [
606
+ type,
607
+ String(payload.source ?? ''),
608
+ String(payload.pagePath ?? ''),
609
+ String(payload.message ?? ''),
610
+ String(payload.errorName ?? ''),
611
+ String(payload.stack ?? ''),
612
+ String(payload.filename ?? ''),
613
+ String(payload.lineno ?? ''),
614
+ String(payload.colno ?? ''),
615
+ String(payload.tagName ?? ''),
616
+ String(payload.resourceUrl ?? ''),
617
+ ];
618
+ return parts.join('|');
619
+ }
572
620
  function installBrowserErrorMonitor(report, options) {
573
621
  if (typeof window === 'undefined')
574
622
  return;
@@ -576,6 +624,7 @@ function installBrowserErrorMonitor(report, options) {
576
624
  if (w.__beLinkClsLoggerErrorInstalled__)
577
625
  return;
578
626
  w.__beLinkClsLoggerErrorInstalled__ = true;
627
+ const shouldReport = createDedupeGuard(options);
579
628
  window.addEventListener('error', (event) => {
580
629
  try {
581
630
  if (!sampleHit$1(options.sampleRate))
@@ -598,6 +647,8 @@ function installBrowserErrorMonitor(report, options) {
598
647
  if (e.stack)
599
648
  payload.stack = e.stack;
600
649
  }
650
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
651
+ return;
601
652
  report(options.reportType, payload);
602
653
  return;
603
654
  }
@@ -609,6 +660,8 @@ function installBrowserErrorMonitor(report, options) {
609
660
  payload.source = 'resource.error';
610
661
  payload.tagName = tagName;
611
662
  payload.resourceUrl = truncate$2(url, 2000);
663
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
664
+ return;
612
665
  report(options.reportType, payload);
613
666
  }
614
667
  }
@@ -629,6 +682,8 @@ function installBrowserErrorMonitor(report, options) {
629
682
  errorName: e.name,
630
683
  stack: e.stack,
631
684
  };
685
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
686
+ return;
632
687
  report(options.reportType, payload);
633
688
  }
634
689
  catch {
@@ -641,6 +696,7 @@ function installMiniProgramErrorMonitor(report, options) {
641
696
  if (g.__beLinkClsLoggerMpErrorInstalled__)
642
697
  return;
643
698
  g.__beLinkClsLoggerMpErrorInstalled__ = true;
699
+ const shouldReport = createDedupeGuard(options);
644
700
  const wxAny = globalThis.wx;
645
701
  // wx.* 事件(兼容在 App 已经创建后的场景)
646
702
  try {
@@ -649,10 +705,13 @@ function installMiniProgramErrorMonitor(report, options) {
649
705
  try {
650
706
  if (!sampleHit$1(options.sampleRate))
651
707
  return;
652
- report(options.reportType, {
708
+ const payload = {
653
709
  source: 'wx.onError',
654
710
  message: truncate$2(String(msg ?? ''), options.maxTextLength),
655
- });
711
+ };
712
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
713
+ return;
714
+ report(options.reportType, payload);
656
715
  }
657
716
  catch {
658
717
  // ignore
@@ -670,12 +729,15 @@ function installMiniProgramErrorMonitor(report, options) {
670
729
  if (!sampleHit$1(options.sampleRate))
671
730
  return;
672
731
  const e = normalizeErrorLike(res?.reason, options.maxTextLength);
673
- report(options.reportType, {
732
+ const payload = {
674
733
  source: 'wx.onUnhandledRejection',
675
734
  message: e.message,
676
735
  errorName: e.name,
677
736
  stack: e.stack,
678
- });
737
+ };
738
+ if (!shouldReport(buildErrorKey(options.reportType, payload)))
739
+ return;
740
+ report(options.reportType, payload);
679
741
  }
680
742
  catch {
681
743
  // ignore
@@ -697,10 +759,12 @@ function installMiniProgramErrorMonitor(report, options) {
697
759
  next.onError = function (...args) {
698
760
  try {
699
761
  if (sampleHit$1(options.sampleRate)) {
700
- report(options.reportType, {
762
+ const payload = {
701
763
  source: 'App.onError',
702
764
  message: truncate$2(String(args?.[0] ?? ''), options.maxTextLength),
703
- });
765
+ };
766
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
767
+ report(options.reportType, payload);
704
768
  }
705
769
  }
706
770
  catch {
@@ -716,12 +780,14 @@ function installMiniProgramErrorMonitor(report, options) {
716
780
  if (sampleHit$1(options.sampleRate)) {
717
781
  const reason = args?.[0]?.reason ?? args?.[0];
718
782
  const e = normalizeErrorLike(reason, options.maxTextLength);
719
- report(options.reportType, {
783
+ const payload = {
720
784
  source: 'App.onUnhandledRejection',
721
785
  message: e.message,
722
786
  errorName: e.name,
723
787
  stack: e.stack,
724
- });
788
+ };
789
+ if (shouldReport(buildErrorKey(options.reportType, payload)))
790
+ report(options.reportType, payload);
725
791
  }
726
792
  }
727
793
  catch {
@@ -750,6 +816,8 @@ function installErrorMonitor(report, opts = {}) {
750
816
  sampleRate: raw.sampleRate ?? 1,
751
817
  captureResourceError: raw.captureResourceError ?? true,
752
818
  maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
819
+ dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
820
+ dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
753
821
  };
754
822
  if (isMiniProgramEnv()) {
755
823
  installMiniProgramErrorMonitor(report, options);
@@ -1826,6 +1894,27 @@ function createAutoDeviceInfoBaseFields(envType, opts) {
1826
1894
  };
1827
1895
  }
1828
1896
 
1897
+ function readGlobal(key) {
1898
+ try {
1899
+ const g = globalThis;
1900
+ return g[key] ?? null;
1901
+ }
1902
+ catch {
1903
+ return null;
1904
+ }
1905
+ }
1906
+ function tryRequire(moduleName) {
1907
+ try {
1908
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
1909
+ const req = (typeof require === 'function' ? require : null);
1910
+ if (!req)
1911
+ return null;
1912
+ return req(moduleName);
1913
+ }
1914
+ catch {
1915
+ return null;
1916
+ }
1917
+ }
1829
1918
  function enterClsSendingGuard() {
1830
1919
  const g = globalThis;
1831
1920
  const next = (g.__beLinkClsLoggerSendingCount__ ?? 0) + 1;
@@ -1837,7 +1926,10 @@ function enterClsSendingGuard() {
1837
1926
  }
1838
1927
  class ClsLogger {
1839
1928
  constructor() {
1929
+ this.sdk = null;
1930
+ this.sdkPromise = null;
1840
1931
  this.client = null;
1932
+ this.clientPromise = null;
1841
1933
  this.topicId = null;
1842
1934
  this.endpoint = 'ap-shanghai.cls.tencentcs.com';
1843
1935
  this.retryTimes = 10;
@@ -1879,6 +1971,19 @@ class ClsLogger {
1879
1971
  console.warn('ClsLogger.init 没有传 topicID/topic_id');
1880
1972
  return;
1881
1973
  }
1974
+ const nextEnvType = options.envType ?? this.detectEnvType();
1975
+ // envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
1976
+ const envChanged = nextEnvType !== this.envType;
1977
+ const endpointChanged = endpoint !== this.endpoint;
1978
+ const retryChanged = retryTimes !== this.retryTimes;
1979
+ if (envChanged || endpointChanged || retryChanged) {
1980
+ this.client = null;
1981
+ this.clientPromise = null;
1982
+ }
1983
+ if (envChanged) {
1984
+ this.sdk = null;
1985
+ this.sdkPromise = null;
1986
+ }
1882
1987
  this.topicId = topicId;
1883
1988
  this.endpoint = endpoint;
1884
1989
  this.retryTimes = retryTimes;
@@ -1889,7 +1994,7 @@ class ClsLogger {
1889
1994
  this.projectName = options.projectName ?? this.projectName;
1890
1995
  this.appId = options.appId ?? this.appId;
1891
1996
  this.appVersion = options.appVersion ?? this.appVersion;
1892
- this.envType = options.envType ?? this.detectEnvType();
1997
+ this.envType = nextEnvType;
1893
1998
  this.userGenerateBaseFields = options.generateBaseFields ?? this.userGenerateBaseFields;
1894
1999
  this.autoGenerateBaseFields = createAutoDeviceInfoBaseFields(this.envType, options.deviceInfo);
1895
2000
  this.storageKey = options.storageKey ?? this.storageKey;
@@ -1899,8 +2004,10 @@ class ClsLogger {
1899
2004
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
1900
2005
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
1901
2006
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
1902
- // 预热 client
1903
- this.getInstance();
2007
+ // 预热(避免首条日志触发 import/初始化开销)
2008
+ void this.getInstance().catch(() => {
2009
+ // ignore
2010
+ });
1904
2011
  // 启动时尝试发送失败缓存
1905
2012
  this.flushFailed();
1906
2013
  // 初始化后立即启动请求监听
@@ -2004,14 +2111,70 @@ class ClsLogger {
2004
2111
  this.behaviorMonitorCleanup = null;
2005
2112
  this.behaviorMonitorStarted = false;
2006
2113
  }
2007
- getInstance() {
2114
+ /**
2115
+ * 获取 CLS client(按环境懒加载 SDK)
2116
+ * - browser: 优先走 UMD 全局变量 `tencentcloudClsSdkJsWeb`
2117
+ * - miniprogram: 优先走 require(webpack/taro 可解析),否则 fallback import()
2118
+ */
2119
+ async getInstance() {
2008
2120
  if (this.client)
2009
2121
  return this.client;
2010
- this.client = new AsyncClient({
2011
- endpoint: this.endpoint,
2012
- retry_times: this.retryTimes,
2122
+ if (this.clientPromise)
2123
+ return this.clientPromise;
2124
+ this.clientPromise = this.loadSdk()
2125
+ .then(({ AsyncClient }) => {
2126
+ const client = new AsyncClient({
2127
+ endpoint: this.endpoint,
2128
+ retry_times: this.retryTimes,
2129
+ });
2130
+ this.client = client;
2131
+ return client;
2132
+ })
2133
+ .catch((err) => {
2134
+ // 失败后允许下次重试
2135
+ this.clientPromise = null;
2136
+ throw err;
2137
+ });
2138
+ return this.clientPromise;
2139
+ }
2140
+ async loadSdk() {
2141
+ if (this.sdk)
2142
+ return this.sdk;
2143
+ if (this.sdkPromise)
2144
+ return this.sdkPromise;
2145
+ const isMini = this.envType === 'miniprogram';
2146
+ const moduleName = isMini ? 'tencentcloud-cls-sdk-js-mini' : 'tencentcloud-cls-sdk-js-web';
2147
+ // UMD(浏览器脚本)优先读全局变量
2148
+ if (!isMini) {
2149
+ const g = readGlobal('tencentcloudClsSdkJsWeb');
2150
+ if (g?.AsyncClient && g?.Log && g?.LogGroup && g?.PutLogsRequest) {
2151
+ this.sdk = g;
2152
+ return this.sdk;
2153
+ }
2154
+ }
2155
+ // 尽量同步 require(小程序/webpack 里最稳),失败再走 import()
2156
+ const reqMod = tryRequire(moduleName);
2157
+ if (reqMod?.AsyncClient && reqMod?.Log && reqMod?.LogGroup && reqMod?.PutLogsRequest) {
2158
+ this.sdk = reqMod;
2159
+ return this.sdk;
2160
+ }
2161
+ this.sdkPromise = import(moduleName)
2162
+ .then((m) => {
2163
+ const mod = (m?.default && m.default.AsyncClient ? m.default : m);
2164
+ const sdk = {
2165
+ AsyncClient: mod.AsyncClient,
2166
+ Log: mod.Log,
2167
+ LogGroup: mod.LogGroup,
2168
+ PutLogsRequest: mod.PutLogsRequest,
2169
+ };
2170
+ this.sdk = sdk;
2171
+ return sdk;
2172
+ })
2173
+ .catch((err) => {
2174
+ this.sdkPromise = null;
2175
+ throw err;
2013
2176
  });
2014
- return this.client;
2177
+ return this.sdkPromise;
2015
2178
  }
2016
2179
  detectEnvType() {
2017
2180
  const wxAny = globalThis.wx;
@@ -2043,22 +2206,33 @@ class ClsLogger {
2043
2206
  appVersion: this.appVersion || undefined,
2044
2207
  ...normalizedFields,
2045
2208
  });
2046
- const client = this.getInstance();
2047
- const logGroup = new LogGroup('127.0.0.1');
2209
+ // 同步 API:内部异步发送,避免把网络异常冒泡到业务(尤其小程序)
2210
+ void this.putAsync(finalFields).catch(() => {
2211
+ // ignore
2212
+ });
2213
+ }
2214
+ async putAsync(finalFields) {
2215
+ if (!this.topicId)
2216
+ return;
2217
+ const sdk = await this.loadSdk();
2218
+ const client = await this.getInstance();
2219
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2048
2220
  logGroup.setSource(this.source);
2049
- const log = new Log(Date.now());
2221
+ const log = new sdk.Log(Date.now());
2050
2222
  for (const key of Object.keys(finalFields)) {
2051
2223
  log.addContent(key, stringifyLogValue(finalFields[key]));
2052
2224
  }
2053
2225
  logGroup.addLog(log);
2054
- const request = new PutLogsRequest(this.topicId, logGroup);
2226
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2055
2227
  const exit = enterClsSendingGuard();
2228
+ let p;
2056
2229
  try {
2057
- client.PutLogs(request);
2230
+ p = client.PutLogs(request);
2058
2231
  }
2059
2232
  finally {
2060
2233
  exit();
2061
2234
  }
2235
+ await p;
2062
2236
  }
2063
2237
  /**
2064
2238
  * 直接上报:把 data 序列化后放入指定 key(默认 “日志内容”)
@@ -2117,11 +2291,19 @@ class ClsLogger {
2117
2291
  console.warn('ClsLogger.putBatch:未初始化 topic_id');
2118
2292
  return;
2119
2293
  }
2120
- const client = this.getInstance();
2121
- const logGroup = new LogGroup('127.0.0.1');
2294
+ void this.putBatchAsync(queue).catch(() => {
2295
+ // ignore
2296
+ });
2297
+ }
2298
+ async putBatchAsync(queue) {
2299
+ if (!this.topicId)
2300
+ return;
2301
+ const sdk = await this.loadSdk();
2302
+ const client = await this.getInstance();
2303
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2122
2304
  logGroup.setSource(this.source);
2123
2305
  for (const item of queue) {
2124
- const log = new Log(item.time);
2306
+ const log = new sdk.Log(item.time);
2125
2307
  const data = item.data ?? {};
2126
2308
  for (const key of Object.keys(data)) {
2127
2309
  log.addContent(key, stringifyLogValue(data[key]));
@@ -2130,14 +2312,16 @@ class ClsLogger {
2130
2312
  }
2131
2313
  if (logGroup.getLogs().length === 0)
2132
2314
  return;
2133
- const request = new PutLogsRequest(this.topicId, logGroup);
2315
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2134
2316
  const exit = enterClsSendingGuard();
2317
+ let p;
2135
2318
  try {
2136
- client.PutLogs(request);
2319
+ p = client.PutLogs(request);
2137
2320
  }
2138
2321
  finally {
2139
2322
  exit();
2140
2323
  }
2324
+ await p;
2141
2325
  }
2142
2326
  /**
2143
2327
  * 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
@@ -2250,12 +2434,13 @@ class ClsLogger {
2250
2434
  async sendReportLogs(logs) {
2251
2435
  if (!this.topicId)
2252
2436
  return;
2253
- const client = this.getInstance();
2254
- const logGroup = new LogGroup('127.0.0.1');
2437
+ const sdk = await this.loadSdk();
2438
+ const client = await this.getInstance();
2439
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2255
2440
  logGroup.setSource(this.source);
2256
2441
  for (const item of logs) {
2257
2442
  const fields = this.buildReportFields(item);
2258
- const log = new Log(fields.timestamp);
2443
+ const log = new sdk.Log(fields.timestamp);
2259
2444
  for (const key of Object.keys(fields)) {
2260
2445
  if (key === 'timestamp')
2261
2446
  continue;
@@ -2263,7 +2448,7 @@ class ClsLogger {
2263
2448
  }
2264
2449
  logGroup.addLog(log);
2265
2450
  }
2266
- const request = new PutLogsRequest(this.topicId, logGroup);
2451
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2267
2452
  // 只在“发起网络请求”的同步阶段打标记,避免 requestMonitor 监控 CLS 上报请求导致递归
2268
2453
  const exit = enterClsSendingGuard();
2269
2454
  let p;