@be-link/cls-logger 1.0.1-beta.4 → 1.0.1-beta.6

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
  }
@@ -1909,9 +1907,20 @@ function enterClsSendingGuard() {
1909
1907
  g.__beLinkClsLoggerSendingCount__ = cur > 0 ? cur - 1 : 0;
1910
1908
  };
1911
1909
  }
1912
- class ClsLogger {
1910
+ /**
1911
+ * CLS Logger 核心基类
1912
+ * - 负责所有上报、队列、监控逻辑
1913
+ * - 不包含具体的 SDK 加载实现(由子类负责)
1914
+ * - 这样可以把 web/mini 的 SDK 依赖彻底解耦到子入口
1915
+ */
1916
+ class ClsLoggerCore {
1913
1917
  constructor() {
1918
+ this.sdk = null;
1919
+ this.sdkPromise = null;
1920
+ this.sdkOverride = null;
1921
+ this.sdkLoaderOverride = null;
1914
1922
  this.client = null;
1923
+ this.clientPromise = null;
1915
1924
  this.topicId = null;
1916
1925
  this.endpoint = 'ap-shanghai.cls.tencentcs.com';
1917
1926
  this.retryTimes = 10;
@@ -1942,6 +1951,15 @@ class ClsLogger {
1942
1951
  this.behaviorMonitorStarted = false;
1943
1952
  this.behaviorMonitorCleanup = null;
1944
1953
  }
1954
+ /**
1955
+ * 子类可按需重写(默认检测 wx)
1956
+ */
1957
+ detectEnvType() {
1958
+ const wxAny = globalThis.wx;
1959
+ if (wxAny && typeof wxAny.getSystemInfoSync === 'function')
1960
+ return 'miniprogram';
1961
+ return 'browser';
1962
+ }
1945
1963
  init(options) {
1946
1964
  this.initTs = Date.now();
1947
1965
  const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId ?? null;
@@ -1953,6 +1971,19 @@ class ClsLogger {
1953
1971
  console.warn('ClsLogger.init 没有传 topicID/topic_id');
1954
1972
  return;
1955
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
+ }
1956
1987
  this.topicId = topicId;
1957
1988
  this.endpoint = endpoint;
1958
1989
  this.retryTimes = retryTimes;
@@ -1963,7 +1994,10 @@ class ClsLogger {
1963
1994
  this.projectName = options.projectName ?? this.projectName;
1964
1995
  this.appId = options.appId ?? this.appId;
1965
1996
  this.appVersion = options.appVersion ?? this.appVersion;
1966
- this.envType = options.envType ?? this.detectEnvType();
1997
+ this.envType = nextEnvType;
1998
+ // 可选:外部注入 SDK(优先级:sdkLoader > sdk)
1999
+ this.sdkLoaderOverride = options.sdkLoader ?? this.sdkLoaderOverride;
2000
+ this.sdkOverride = options.sdk ?? this.sdkOverride;
1967
2001
  this.userGenerateBaseFields = options.generateBaseFields ?? this.userGenerateBaseFields;
1968
2002
  this.autoGenerateBaseFields = createAutoDeviceInfoBaseFields(this.envType, options.deviceInfo);
1969
2003
  this.storageKey = options.storageKey ?? this.storageKey;
@@ -1973,8 +2007,10 @@ class ClsLogger {
1973
2007
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
1974
2008
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
1975
2009
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
1976
- // 预热 client
1977
- this.getInstance();
2010
+ // 预热(避免首条日志触发 import/初始化开销)
2011
+ void this.getInstance().catch(() => {
2012
+ // ignore
2013
+ });
1978
2014
  // 启动时尝试发送失败缓存
1979
2015
  this.flushFailed();
1980
2016
  // 初始化后立即启动请求监听
@@ -2078,20 +2114,29 @@ class ClsLogger {
2078
2114
  this.behaviorMonitorCleanup = null;
2079
2115
  this.behaviorMonitorStarted = false;
2080
2116
  }
2081
- getInstance() {
2117
+ /**
2118
+ * 获取 CLS client(按环境懒加载 SDK)
2119
+ */
2120
+ async getInstance() {
2082
2121
  if (this.client)
2083
2122
  return this.client;
2084
- this.client = new tencentcloudClsSdkJsWeb.AsyncClient({
2085
- endpoint: this.endpoint,
2086
- retry_times: this.retryTimes,
2123
+ if (this.clientPromise)
2124
+ return this.clientPromise;
2125
+ this.clientPromise = this.loadSdk()
2126
+ .then(({ AsyncClient }) => {
2127
+ const client = new AsyncClient({
2128
+ endpoint: this.endpoint,
2129
+ retry_times: this.retryTimes,
2130
+ });
2131
+ this.client = client;
2132
+ return client;
2133
+ })
2134
+ .catch((err) => {
2135
+ // 失败后允许下次重试
2136
+ this.clientPromise = null;
2137
+ throw err;
2087
2138
  });
2088
- return this.client;
2089
- }
2090
- detectEnvType() {
2091
- const wxAny = globalThis.wx;
2092
- if (wxAny && typeof wxAny.getSystemInfoSync === 'function')
2093
- return 'miniprogram';
2094
- return 'browser';
2139
+ return this.clientPromise;
2095
2140
  }
2096
2141
  /**
2097
2142
  * 直接上报:埋点入参必须是一维(扁平)Object
@@ -2117,22 +2162,33 @@ class ClsLogger {
2117
2162
  appVersion: this.appVersion || undefined,
2118
2163
  ...normalizedFields,
2119
2164
  });
2120
- const client = this.getInstance();
2121
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2165
+ // 同步 API:内部异步发送,避免把网络异常冒泡到业务(尤其小程序)
2166
+ void this.putAsync(finalFields).catch(() => {
2167
+ // ignore
2168
+ });
2169
+ }
2170
+ async putAsync(finalFields) {
2171
+ if (!this.topicId)
2172
+ return;
2173
+ const sdk = await this.loadSdk();
2174
+ const client = await this.getInstance();
2175
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2122
2176
  logGroup.setSource(this.source);
2123
- const log = new tencentcloudClsSdkJsWeb.Log(Date.now());
2177
+ const log = new sdk.Log(Date.now());
2124
2178
  for (const key of Object.keys(finalFields)) {
2125
2179
  log.addContent(key, stringifyLogValue(finalFields[key]));
2126
2180
  }
2127
2181
  logGroup.addLog(log);
2128
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2182
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2129
2183
  const exit = enterClsSendingGuard();
2184
+ let p;
2130
2185
  try {
2131
- client.PutLogs(request);
2186
+ p = client.PutLogs(request);
2132
2187
  }
2133
2188
  finally {
2134
2189
  exit();
2135
2190
  }
2191
+ await p;
2136
2192
  }
2137
2193
  /**
2138
2194
  * 直接上报:把 data 序列化后放入指定 key(默认 “日志内容”)
@@ -2191,11 +2247,19 @@ class ClsLogger {
2191
2247
  console.warn('ClsLogger.putBatch:未初始化 topic_id');
2192
2248
  return;
2193
2249
  }
2194
- const client = this.getInstance();
2195
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2250
+ void this.putBatchAsync(queue).catch(() => {
2251
+ // ignore
2252
+ });
2253
+ }
2254
+ async putBatchAsync(queue) {
2255
+ if (!this.topicId)
2256
+ return;
2257
+ const sdk = await this.loadSdk();
2258
+ const client = await this.getInstance();
2259
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2196
2260
  logGroup.setSource(this.source);
2197
2261
  for (const item of queue) {
2198
- const log = new tencentcloudClsSdkJsWeb.Log(item.time);
2262
+ const log = new sdk.Log(item.time);
2199
2263
  const data = item.data ?? {};
2200
2264
  for (const key of Object.keys(data)) {
2201
2265
  log.addContent(key, stringifyLogValue(data[key]));
@@ -2204,14 +2268,16 @@ class ClsLogger {
2204
2268
  }
2205
2269
  if (logGroup.getLogs().length === 0)
2206
2270
  return;
2207
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2271
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2208
2272
  const exit = enterClsSendingGuard();
2273
+ let p;
2209
2274
  try {
2210
- client.PutLogs(request);
2275
+ p = client.PutLogs(request);
2211
2276
  }
2212
2277
  finally {
2213
2278
  exit();
2214
2279
  }
2280
+ await p;
2215
2281
  }
2216
2282
  /**
2217
2283
  * 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
@@ -2324,12 +2390,13 @@ class ClsLogger {
2324
2390
  async sendReportLogs(logs) {
2325
2391
  if (!this.topicId)
2326
2392
  return;
2327
- const client = this.getInstance();
2328
- const logGroup = new tencentcloudClsSdkJsWeb.LogGroup('127.0.0.1');
2393
+ const sdk = await this.loadSdk();
2394
+ const client = await this.getInstance();
2395
+ const logGroup = new sdk.LogGroup('127.0.0.1');
2329
2396
  logGroup.setSource(this.source);
2330
2397
  for (const item of logs) {
2331
2398
  const fields = this.buildReportFields(item);
2332
- const log = new tencentcloudClsSdkJsWeb.Log(fields.timestamp);
2399
+ const log = new sdk.Log(fields.timestamp);
2333
2400
  for (const key of Object.keys(fields)) {
2334
2401
  if (key === 'timestamp')
2335
2402
  continue;
@@ -2337,7 +2404,7 @@ class ClsLogger {
2337
2404
  }
2338
2405
  logGroup.addLog(log);
2339
2406
  }
2340
- const request = new tencentcloudClsSdkJsWeb.PutLogsRequest(this.topicId, logGroup);
2407
+ const request = new sdk.PutLogsRequest(this.topicId, logGroup);
2341
2408
  // 只在“发起网络请求”的同步阶段打标记,避免 requestMonitor 监控 CLS 上报请求导致递归
2342
2409
  const exit = enterClsSendingGuard();
2343
2410
  let p;
@@ -2413,6 +2480,122 @@ class ClsLogger {
2413
2480
  }
2414
2481
  }
2415
2482
 
2483
+ function readGlobal(key) {
2484
+ try {
2485
+ const g = globalThis;
2486
+ return g[key] ?? null;
2487
+ }
2488
+ catch {
2489
+ return null;
2490
+ }
2491
+ }
2492
+ function tryRequire(moduleName) {
2493
+ try {
2494
+ // 说明:
2495
+ // - ESM 构建(exports.import/module)里通常不存在模块作用域的 require
2496
+ // - 一些小程序运行时/构建链路会把 require 挂到 globalThis 上
2497
+ // 因此这里同时探测“模块作用域 require”与 “globalThis.require”
2498
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
2499
+ const localReq = (typeof require === 'function' ? require : null);
2500
+ const globalReq = readGlobal('require');
2501
+ const candidates = [localReq, globalReq].filter((fn) => typeof fn === 'function');
2502
+ for (const fn of candidates) {
2503
+ try {
2504
+ return fn(moduleName);
2505
+ }
2506
+ catch {
2507
+ // continue
2508
+ }
2509
+ }
2510
+ return null;
2511
+ }
2512
+ catch {
2513
+ return null;
2514
+ }
2515
+ }
2516
+
2517
+ /**
2518
+ * 兼容版 ClsLogger(默认入口)
2519
+ * - 保留了自动识别环境 + 动态 import 的逻辑
2520
+ * - 如果业务想瘦身,建议改用 @be-link/cls-logger/web 或 /mini
2521
+ */
2522
+ class ClsLogger extends ClsLoggerCore {
2523
+ async loadSdk() {
2524
+ if (this.sdk)
2525
+ return this.sdk;
2526
+ if (this.sdkPromise)
2527
+ return this.sdkPromise;
2528
+ const normalizeSdk = (m) => {
2529
+ const mod = (m?.default && m.default.AsyncClient ? m.default : m);
2530
+ if (mod?.AsyncClient && mod?.Log && mod?.LogGroup && mod?.PutLogsRequest) {
2531
+ return {
2532
+ AsyncClient: mod.AsyncClient,
2533
+ Log: mod.Log,
2534
+ LogGroup: mod.LogGroup,
2535
+ PutLogsRequest: mod.PutLogsRequest,
2536
+ };
2537
+ }
2538
+ return null;
2539
+ };
2540
+ // 1) 外部注入的 loader(最高优先级)
2541
+ if (this.sdkLoaderOverride) {
2542
+ try {
2543
+ const loaded = await this.sdkLoaderOverride();
2544
+ const sdk = normalizeSdk(loaded);
2545
+ if (sdk) {
2546
+ this.sdk = sdk;
2547
+ return sdk;
2548
+ }
2549
+ }
2550
+ catch {
2551
+ // ignore and fallback
2552
+ }
2553
+ }
2554
+ // 2) 外部直接注入的 sdk
2555
+ if (this.sdkOverride) {
2556
+ const sdk = normalizeSdk(this.sdkOverride);
2557
+ if (sdk) {
2558
+ this.sdk = sdk;
2559
+ return sdk;
2560
+ }
2561
+ }
2562
+ const isMini = this.envType === 'miniprogram';
2563
+ // 静态字面量,方便打包器识别(但在此兼容入口里,仍然可能被一起分析)
2564
+ const WEB_SDK = 'tencentcloud-cls-sdk-js-web';
2565
+ const MINI_SDK = 'tencentcloud-cls-sdk-js-mini';
2566
+ // UMD(浏览器脚本)优先读全局变量
2567
+ if (!isMini) {
2568
+ const g = readGlobal('tencentcloudClsSdkJsWeb');
2569
+ const sdk = normalizeSdk(g);
2570
+ if (sdk) {
2571
+ this.sdk = sdk;
2572
+ return sdk;
2573
+ }
2574
+ }
2575
+ // 尽量同步 require
2576
+ const reqMod = tryRequire(isMini ? MINI_SDK : WEB_SDK);
2577
+ const reqSdk = normalizeSdk(reqMod);
2578
+ if (reqSdk) {
2579
+ this.sdk = reqSdk;
2580
+ return reqSdk;
2581
+ }
2582
+ // 动态 import
2583
+ this.sdkPromise = (isMini ? import(MINI_SDK) : import(WEB_SDK))
2584
+ .then((m) => {
2585
+ const sdk = normalizeSdk(m);
2586
+ if (!sdk)
2587
+ throw new Error(`ClsLogger.loadSdk: invalid sdk module for ${isMini ? MINI_SDK : WEB_SDK}`);
2588
+ this.sdk = sdk;
2589
+ return sdk;
2590
+ })
2591
+ .catch((err) => {
2592
+ this.sdkPromise = null;
2593
+ throw err;
2594
+ });
2595
+ return this.sdkPromise;
2596
+ }
2597
+ }
2598
+
2416
2599
  const clsLogger = new ClsLogger();
2417
2600
 
2418
2601
  exports.ClsLogger = ClsLogger;