@pluve/mp-logger-sdk 0.0.1

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.
Files changed (84) hide show
  1. package/README.md +133 -0
  2. package/dist/cjs/capture/wechatError.js +85 -0
  3. package/dist/cjs/compress/compression.js +94 -0
  4. package/dist/cjs/config/index.js +48 -0
  5. package/dist/cjs/core/fingerprint.js +32 -0
  6. package/dist/cjs/core/httpClient.js +55 -0
  7. package/dist/cjs/core/loggerSDK.js +580 -0
  8. package/dist/cjs/core/queueManager.js +121 -0
  9. package/dist/cjs/core/retryManager.js +123 -0
  10. package/dist/cjs/index.js +25 -0
  11. package/dist/cjs/stack/stacktrace.js +69 -0
  12. package/dist/cjs/transport/transport.js +17 -0
  13. package/dist/cjs/transport/transportAdapter.js +39 -0
  14. package/dist/cjs/transport/wechatTransport.js +98 -0
  15. package/dist/cjs/types/api.js +17 -0
  16. package/dist/cjs/types/env.js +17 -0
  17. package/dist/cjs/types/external.d.ts +15 -0
  18. package/dist/cjs/types/logEvent.js +17 -0
  19. package/dist/cjs/types/logEventLevel.js +17 -0
  20. package/dist/cjs/types/sdkOptions.js +17 -0
  21. package/dist/cjs/types/securityType.js +34 -0
  22. package/dist/cjs/types/trackOptions.js +17 -0
  23. package/dist/cjs/utils/environment.js +85 -0
  24. package/dist/cjs/utils/innerMD5.js +143 -0
  25. package/dist/cjs/utils/session.js +27 -0
  26. package/dist/cjs/utils/tools.js +73 -0
  27. package/dist/cjs/utils/uuid.js +31 -0
  28. package/dist/esm/capture/wechatError.js +65 -0
  29. package/dist/esm/compress/compression.js +74 -0
  30. package/dist/esm/config/index.js +28 -0
  31. package/dist/esm/core/fingerprint.js +12 -0
  32. package/dist/esm/core/httpClient.js +35 -0
  33. package/dist/esm/core/loggerSDK.js +560 -0
  34. package/dist/esm/core/queueManager.js +101 -0
  35. package/dist/esm/core/retryManager.js +103 -0
  36. package/dist/esm/index.js +5 -0
  37. package/dist/esm/stack/stacktrace.js +49 -0
  38. package/dist/esm/transport/transport.js +0 -0
  39. package/dist/esm/transport/transportAdapter.js +19 -0
  40. package/dist/esm/transport/wechatTransport.js +78 -0
  41. package/dist/esm/types/api.js +0 -0
  42. package/dist/esm/types/env.js +0 -0
  43. package/dist/esm/types/external.d.ts +15 -0
  44. package/dist/esm/types/logEvent.js +0 -0
  45. package/dist/esm/types/logEventLevel.js +0 -0
  46. package/dist/esm/types/sdkOptions.js +0 -0
  47. package/dist/esm/types/securityType.js +14 -0
  48. package/dist/esm/types/trackOptions.js +0 -0
  49. package/dist/esm/utils/environment.js +65 -0
  50. package/dist/esm/utils/innerMD5.js +123 -0
  51. package/dist/esm/utils/session.js +7 -0
  52. package/dist/esm/utils/tools.js +53 -0
  53. package/dist/esm/utils/uuid.js +11 -0
  54. package/dist/types/capture/jsError.d.ts +2 -0
  55. package/dist/types/capture/promiseError.d.ts +2 -0
  56. package/dist/types/capture/resourceError.d.ts +2 -0
  57. package/dist/types/capture/wechatError.d.ts +3 -0
  58. package/dist/types/compress/compression.d.ts +9 -0
  59. package/dist/types/config/index.d.ts +9 -0
  60. package/dist/types/core/fingerprint.d.ts +8 -0
  61. package/dist/types/core/httpClient.d.ts +11 -0
  62. package/dist/types/core/loggerSDK.d.ts +87 -0
  63. package/dist/types/core/queueManager.d.ts +55 -0
  64. package/dist/types/core/retryManager.d.ts +53 -0
  65. package/dist/types/index.d.ts +2 -0
  66. package/dist/types/stack/stacktrace.d.ts +2 -0
  67. package/dist/types/transport/beaconTransport.d.ts +11 -0
  68. package/dist/types/transport/pixelImageTransport.d.ts +11 -0
  69. package/dist/types/transport/transport.d.ts +14 -0
  70. package/dist/types/transport/transportAdapter.d.ts +8 -0
  71. package/dist/types/transport/wechatTransport.d.ts +11 -0
  72. package/dist/types/types/api.d.ts +12 -0
  73. package/dist/types/types/env.d.ts +14 -0
  74. package/dist/types/types/logEvent.d.ts +57 -0
  75. package/dist/types/types/logEventLevel.d.ts +2 -0
  76. package/dist/types/types/sdkOptions.d.ts +61 -0
  77. package/dist/types/types/securityType.d.ts +6 -0
  78. package/dist/types/types/trackOptions.d.ts +7 -0
  79. package/dist/types/utils/environment.d.ts +10 -0
  80. package/dist/types/utils/innerMD5.d.ts +6 -0
  81. package/dist/types/utils/session.d.ts +1 -0
  82. package/dist/types/utils/tools.d.ts +12 -0
  83. package/dist/types/utils/uuid.d.ts +7 -0
  84. package/package.json +65 -0
@@ -0,0 +1,560 @@
1
+ // src/core/loggerSDK.ts
2
+ import { getRegisterApi } from "../config";
3
+ import { TransportAdapter } from "../transport/transportAdapter";
4
+ import { collectEnvironmentTags, getCurrentUrl } from "../utils/environment";
5
+ import { registerWechatErrorCapture, registerWechatUnhandledCapture } from "../capture/wechatError";
6
+ import { getSessionId } from "../utils/session";
7
+ import { flattenStack, hashToProb, logDebug, now, normalizeMessage } from "../utils/tools";
8
+ import { uuid } from "../utils/uuid";
9
+ import { HttpClient } from "./httpClient";
10
+ import { QueueManager } from "./queueManager";
11
+ import { RetryManager } from "./retryManager";
12
+ import { md5 } from "../utils/innerMD5";
13
+ var LoggerSDK = class {
14
+ constructor() {
15
+ /** 事件序列编号,用于事件去重 */
16
+ this.seq = 0;
17
+ /** 是否已关闭 */
18
+ this.closed = false;
19
+ /** 预收集的环境信息 tags(仅用于初始化上报) */
20
+ this.envTags = {};
21
+ /** 是否已初始化 */
22
+ this.initialized = false;
23
+ /** 是否正在上报 */
24
+ this.isSending = false;
25
+ this.collectSwitch = 0;
26
+ // 采集开关,0:关闭,1:开启
27
+ this.collectLogLevel = [];
28
+ // 采集日志等级
29
+ this.recentAutoEvents = /* @__PURE__ */ new Map();
30
+ this.sessionId = getSessionId();
31
+ }
32
+ static getInstance() {
33
+ if (!LoggerSDK.instance) {
34
+ LoggerSDK.instance = new LoggerSDK();
35
+ }
36
+ return LoggerSDK.instance;
37
+ }
38
+ /**
39
+ * 初始化:配置参数、收集环境信息与 UA,并生成 sessionId,完成初始上报
40
+ */
41
+ init(options) {
42
+ this.opts = {
43
+ // endpoint: options.endpoint,
44
+ appId: `${options.appId}`,
45
+ // 统一与 zadig 部署应用 APPID 保持一致
46
+ env: options.env || "develop",
47
+ /** 日志环境 */
48
+ logStage: options.logStage,
49
+ debug: !!options.debug,
50
+ /** 是否启用 gzip 压缩,默认 true */
51
+ enableGzip: options.enableGzip !== false,
52
+ // 默认启用,但仅在批量模式开启情况下有效
53
+ gzipBatchMinSize: options.gzipBatchMinSize || 2,
54
+ // 默认 2 条数据开启 gzip 压缩
55
+ /** 最大像素图 URL 长度,默认 1900 */
56
+ maxPixelUrlLen: options.maxPixelUrlLen || 8192,
57
+ // 批量上报配置(默认关闭,需显式开启)
58
+ enableBatch: options.enableBatch === true,
59
+ batchSize: options.batchSize || 20,
60
+ batchInterval: options.batchInterval || 15e3,
61
+ // 默认 15 秒批量上报一次,在开启批量上报的情况下
62
+ maxQueueSize: options.maxQueueSize || 100,
63
+ // 持久化存储配置
64
+ enableStorage: options.enableBatch === true || options.enableStorage !== false,
65
+ // 默认启用
66
+ storagePrefix: options.storagePrefix || "logger_sdk",
67
+ // 重试配置
68
+ enableRetry: options.enableRetry !== false,
69
+ // 默认启用
70
+ maxRetries: options.maxRetries || 3,
71
+ retryDelay: options.retryDelay || 1e3,
72
+ retryBackoff: options.retryBackoff !== false,
73
+ // 默认启用
74
+ userId: options.userId,
75
+ storeCode: options.storeCode,
76
+ token: options.token,
77
+ sampleRate: typeof options.sampleRate === "number" ? options.sampleRate : 1,
78
+ levelSampleRate: options.levelSampleRate || {
79
+ ERROR: 0.8,
80
+ WARN: 0.5,
81
+ INFO: 0.1,
82
+ FATAL: 1
83
+ },
84
+ sourceSampleRate: options.sourceSampleRate || {
85
+ js: 0.8,
86
+ promise: 0.5,
87
+ resource: 0.2,
88
+ custom: 1
89
+ },
90
+ pathSampleRate: options.pathSampleRate || {
91
+ "/": 1
92
+ },
93
+ enableAutoCapture: options.enableAutoCapture !== false,
94
+ // 默认启用
95
+ autoCapture: options.autoCapture || {
96
+ js: true,
97
+ promise: true,
98
+ resource: true,
99
+ wechat: true
100
+ }
101
+ };
102
+ if (this.opts.enableBatch) {
103
+ this.queueManager = new QueueManager({
104
+ maxSize: this.opts.maxQueueSize,
105
+ enableStorage: this.opts.enableStorage,
106
+ storagePrefix: this.opts.storagePrefix,
107
+ debug: this.opts.debug
108
+ });
109
+ }
110
+ if (this.opts.enableRetry) {
111
+ this.retryManager = new RetryManager({
112
+ maxRetries: this.opts.maxRetries,
113
+ baseDelay: this.opts.retryDelay,
114
+ useBackoff: this.opts.retryBackoff,
115
+ debug: this.opts.debug
116
+ });
117
+ }
118
+ this.transporter = void 0;
119
+ this.sessionId = getSessionId();
120
+ this.envTags = collectEnvironmentTags();
121
+ this.initSDK(this.opts, (data) => {
122
+ var _a, _b, _c, _d, _e;
123
+ this.collectSwitch = data.collectSwitch;
124
+ this.collectLogLevel = data.collectLogLevel;
125
+ this.opts.sampleRate = data.samplingRate;
126
+ if ((_a = this.opts) == null ? void 0 : _a.enableBatch) {
127
+ this.startBatchTimer();
128
+ }
129
+ this.initialized = true;
130
+ const enableAuto = ((_b = this.opts) == null ? void 0 : _b.enableAutoCapture) !== false;
131
+ const ac = ((_c = this.opts) == null ? void 0 : _c.autoCapture) || {};
132
+ if (enableAuto && ac.wechat !== false) {
133
+ this.offWxError = registerWechatErrorCapture(!!((_d = this.opts) == null ? void 0 : _d.debug), this.trackInner.bind(this));
134
+ this.offWxUnhandled = registerWechatUnhandledCapture(!!((_e = this.opts) == null ? void 0 : _e.debug), this.trackInner.bind(this));
135
+ }
136
+ });
137
+ }
138
+ /**
139
+ * 设置 token 信息
140
+ * @param token token
141
+ */
142
+ setToken(token) {
143
+ var _a;
144
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "setToken", token);
145
+ if (this.opts) {
146
+ this.opts.token = token;
147
+ }
148
+ }
149
+ /**
150
+ * 设置用户信息
151
+ */
152
+ identify(userId) {
153
+ var _a;
154
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "identify", userId);
155
+ if (this.opts) {
156
+ this.opts.userId = userId;
157
+ }
158
+ }
159
+ /** 设置店铺编码 */
160
+ setStoreCode(storeCode) {
161
+ var _a;
162
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "setStoreCode", storeCode);
163
+ if (this.opts) {
164
+ this.opts.storeCode = storeCode;
165
+ }
166
+ }
167
+ /** 设置日志 stage(env) */
168
+ setStage(stage) {
169
+ var _a;
170
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "setStage", stage);
171
+ if (this.opts) {
172
+ this.opts.env = stage;
173
+ }
174
+ }
175
+ flattenEnvTags() {
176
+ return Object.entries(this.envTags || {}).map(([k, v]) => `${k}=${v}`).join(",");
177
+ }
178
+ async track({ message, error, traceId, logLevel = "INFO" }) {
179
+ var _a, _b, _c;
180
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "track", { message, error, traceId, logLevel });
181
+ if (this.closed)
182
+ return;
183
+ if (!this.initialized) {
184
+ logDebug(!!((_b = this.opts) == null ? void 0 : _b.debug), "SDK not initialized, skip track");
185
+ return;
186
+ }
187
+ if (!this.shouldSend(logLevel, "custom", getCurrentUrl(), traceId, message)) {
188
+ logDebug(!!((_c = this.opts) == null ? void 0 : _c.debug), "Event sampled out", { logLevel, traceId, message });
189
+ return;
190
+ }
191
+ this.seq += 1;
192
+ let errorLocation = "";
193
+ let throwableStack = "";
194
+ if (error instanceof Error) {
195
+ try {
196
+ const { location, throwable } = await flattenStack(error);
197
+ errorLocation = location || "";
198
+ throwableStack = throwable || error.stack || "";
199
+ } catch {
200
+ errorLocation = "";
201
+ throwableStack = error.stack || "";
202
+ }
203
+ }
204
+ const logEvent = {
205
+ /** 日志 ID */
206
+ logId: `${this.opts.appId}${uuid()}${now()}`,
207
+ // UUID
208
+ seq: this.seq,
209
+ /** 应用标识 */
210
+ appId: this.opts.appId || "unknown",
211
+ /** 环境标识 */
212
+ stage: this.opts.logStage,
213
+ /** 日志级别:info/warn/error/fatal */
214
+ level: logLevel,
215
+ /** 可选:跟踪 ID(跨服务调用链跟踪) */
216
+ traceId,
217
+ /** 会话标识,同 sessionId */
218
+ frontendId: this.sessionId,
219
+ // 同 sessionId
220
+ /** 发生页面 URL */
221
+ url: getCurrentUrl(),
222
+ location: errorLocation,
223
+ /** 异常信息 */
224
+ message: `[message]: ${message}; [envTags]: ${this.flattenEnvTags()}; [t]: ${now()}`,
225
+ /** 可选:堆栈信息(长字符串) */
226
+ throwable: throwableStack,
227
+ /** 可选:用户 ID(脱敏) */
228
+ userId: this.opts.userId,
229
+ /** 可选:店铺编码 */
230
+ storeCode: this.opts.storeCode
231
+ /** 可选的结构化额外信息 */
232
+ // tags: this.envTags,
233
+ };
234
+ this.doTrack(logEvent);
235
+ }
236
+ trackInner(error) {
237
+ var _a, _b, _c, _d, _e, _f, _g, _h;
238
+ if (this.closed)
239
+ return;
240
+ if (!this.initialized) {
241
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "SDK not initialized, skip track");
242
+ return;
243
+ }
244
+ logDebug(!!((_b = this.opts) == null ? void 0 : _b.debug), "trackInner", error);
245
+ const sig = `${error.type}|${normalizeMessage(error.message)}|${String(this.sessionId || "")}`;
246
+ const ts = now();
247
+ const last = this.recentAutoEvents.get(sig) || 0;
248
+ if (ts - last < 3e3) {
249
+ return;
250
+ }
251
+ this.recentAutoEvents.set(sig, ts);
252
+ if (!this.shouldSend("ERROR", error.type, getCurrentUrl(), void 0, error.message)) {
253
+ logDebug(!!((_c = this.opts) == null ? void 0 : _c.debug), "Auto-captured event sampled out", error.message);
254
+ return;
255
+ }
256
+ this.seq += 1;
257
+ let errorLocation = "";
258
+ const throwableStack = ((_d = error.stack) == null ? void 0 : _d.map((frame) => `${frame.file}:${frame.line}:${frame.column}`).join("\n ")) || "";
259
+ if (error.stack && error.stack.length > 0) {
260
+ errorLocation = `${(_e = error.stack) == null ? void 0 : _e[0].file}:${(_f = error.stack) == null ? void 0 : _f[0].line}:${(_g = error.stack) == null ? void 0 : _g[0].column}:${(_h = error.stack) == null ? void 0 : _h[0].function}`;
261
+ }
262
+ const logEvent = {
263
+ /** 日志 ID */
264
+ logId: `${this.opts.appId}${uuid()}${now()}`,
265
+ // UUID
266
+ seq: this.seq,
267
+ /** 应用标识 */
268
+ appId: this.opts.appId || "unknown",
269
+ /** 环境标识 */
270
+ stage: this.opts.env || "develop",
271
+ /** 日志级别:info/warn/error/fatal */
272
+ level: "ERROR",
273
+ /** 会话标识,同 sessionId */
274
+ frontendId: this.sessionId,
275
+ // 同 sessionId
276
+ /** 发生页面 URL */
277
+ url: getCurrentUrl(),
278
+ location: errorLocation,
279
+ /** 异常信息 */
280
+ message: `[message]: ${error.message}; [envTags]: ${this.flattenEnvTags()}; [t]: ${now()}`,
281
+ /** 可选:堆栈信息(长字符串) */
282
+ throwable: throwableStack,
283
+ /** 可选:用户 ID(脱敏) */
284
+ userId: this.opts.userId,
285
+ /** 可选:店铺编码 */
286
+ storeCode: this.opts.storeCode
287
+ /** 可选的结构化额外信息 */
288
+ // tags: this.envTags,
289
+ };
290
+ this.doTrack(logEvent);
291
+ }
292
+ /**
293
+ * 判断是否应该发送事件
294
+ */
295
+ shouldSend(level, source, url, traceId, message) {
296
+ var _a, _b, _c, _d, _e, _f, _g;
297
+ if (level === "FATAL")
298
+ return true;
299
+ if (this.collectSwitch === 0) {
300
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "Collect switch is off, skip track");
301
+ return false;
302
+ }
303
+ if (!this.collectLogLevel.some((l) => l === level)) {
304
+ logDebug(!!((_b = this.opts) == null ? void 0 : _b.debug), `Log level(${level}) not in collect log level, skip track`);
305
+ return false;
306
+ }
307
+ const levelRate = (_d = (_c = this.opts) == null ? void 0 : _c.levelSampleRate) == null ? void 0 : _d[level];
308
+ const sourceRate = (_f = (_e = this.opts) == null ? void 0 : _e.sourceSampleRate) == null ? void 0 : _f[source];
309
+ let pathRate;
310
+ const pathMap = ((_g = this.opts) == null ? void 0 : _g.pathSampleRate) || {};
311
+ const prefixes = Object.keys(pathMap);
312
+ if (prefixes.length) {
313
+ let best = "";
314
+ for (let i = 0; i < prefixes.length; i += 1) {
315
+ const p2 = prefixes[i];
316
+ if (url.startsWith(p2) && p2.length >= best.length)
317
+ best = p2;
318
+ }
319
+ if (best)
320
+ pathRate = pathMap[best];
321
+ }
322
+ const baseRate = (() => {
323
+ var _a2;
324
+ if (typeof levelRate === "number")
325
+ return levelRate;
326
+ if (typeof sourceRate === "number")
327
+ return sourceRate;
328
+ if (typeof pathRate === "number")
329
+ return pathRate;
330
+ const sr = (_a2 = this.opts) == null ? void 0 : _a2.sampleRate;
331
+ return typeof sr === "number" ? sr : 1;
332
+ })();
333
+ const rate = Math.max(0, Math.min(1, baseRate));
334
+ if (rate >= 1)
335
+ return true;
336
+ const seed = `${String(traceId || "")}|${String(message || "")}|${String(this.sessionId || "")}|${String(level)}|${String(source)}|${String(url)}`;
337
+ const p = hashToProb(seed);
338
+ return p < rate;
339
+ }
340
+ /**
341
+ * 记录事件
342
+ */
343
+ async doTrack(logEvent) {
344
+ var _a, _b, _c;
345
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "track", logEvent);
346
+ if (this.opts.enableBatch && this.queueManager) {
347
+ this.queueManager.enqueue(logEvent);
348
+ if (this.queueManager.size() >= this.opts.batchSize) {
349
+ logDebug(!!((_b = this.opts) == null ? void 0 : _b.debug), "Queue size reached batch size, flushing immediately");
350
+ await this.flush();
351
+ }
352
+ } else {
353
+ try {
354
+ await this.sendEvent(logEvent);
355
+ } catch (e) {
356
+ logDebug(!!((_c = this.opts) == null ? void 0 : _c.debug), "track failed", e);
357
+ }
358
+ }
359
+ }
360
+ /**
361
+ * 手动刷新队列,立即上报所有待发送日志
362
+ */
363
+ async flush() {
364
+ var _a, _b, _c, _d;
365
+ if (!this.queueManager || this.queueManager.size() === 0) {
366
+ return;
367
+ }
368
+ if (this.isSending) {
369
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), "Already sending, skip flush");
370
+ return;
371
+ }
372
+ this.isSending = true;
373
+ logDebug(!!((_b = this.opts) == null ? void 0 : _b.debug), `Flushing ${this.queueManager.size()} events`);
374
+ try {
375
+ const events = this.queueManager.peek(this.queueManager.size());
376
+ if (events.length === 0) {
377
+ return;
378
+ }
379
+ await this.sendBatch(events);
380
+ this.queueManager.dequeue(events.length);
381
+ logDebug(!!((_c = this.opts) == null ? void 0 : _c.debug), `Flushed ${events.length} events successfully`);
382
+ } catch (error) {
383
+ logDebug(!!((_d = this.opts) == null ? void 0 : _d.debug), "Flush failed", error);
384
+ } finally {
385
+ this.isSending = false;
386
+ }
387
+ }
388
+ /**
389
+ * 销毁实例
390
+ */
391
+ async destroy() {
392
+ this.closed = true;
393
+ if (this.batchTimer) {
394
+ clearInterval(this.batchTimer);
395
+ this.batchTimer = null;
396
+ }
397
+ await this.flush();
398
+ if (this.queueManager) {
399
+ this.queueManager.clear();
400
+ }
401
+ if (this.retryManager) {
402
+ this.retryManager.clear();
403
+ }
404
+ if (this.offWxError) {
405
+ this.offWxError();
406
+ this.offWxError = void 0;
407
+ }
408
+ if (this.offWxUnhandled) {
409
+ this.offWxUnhandled();
410
+ this.offWxUnhandled = void 0;
411
+ }
412
+ this.initialized = false;
413
+ this.sessionId = void 0;
414
+ LoggerSDK.instance = void 0;
415
+ }
416
+ generateToken() {
417
+ var _a;
418
+ return ((_a = this.opts) == null ? void 0 : _a.token) || md5(`${this.opts.appId}${this.opts.logStage}${now()}`);
419
+ }
420
+ // ========== 内部方法 ===========
421
+ /**
422
+ * 发送单个事件(带重试)
423
+ */
424
+ async sendEvent(event) {
425
+ const sendFn = async () => {
426
+ const transporter = this.transporter || await TransportAdapter.getInstance(this.opts).getTransporter();
427
+ this.transporter = transporter;
428
+ await transporter.send(this.generateToken(), {
429
+ appId: event.appId,
430
+ appStage: event.stage,
431
+ items: [
432
+ {
433
+ level: event.level === "FATAL" ? "ERROR" : event.level,
434
+ traceId: event.traceId,
435
+ frontendId: event.frontendId,
436
+ location: event.location,
437
+ message: event.message,
438
+ throwable: event.throwable
439
+ }
440
+ ]
441
+ });
442
+ };
443
+ if (this.opts.enableRetry && this.retryManager) {
444
+ await this.retryManager.executeWithRetry(event.logId, sendFn);
445
+ } else {
446
+ await sendFn();
447
+ }
448
+ }
449
+ /**
450
+ * 批量发送事件(带重试)
451
+ */
452
+ async sendBatch(events) {
453
+ var _a;
454
+ if (events.length === 0)
455
+ return;
456
+ const groups = /* @__PURE__ */ new Map();
457
+ for (let i = 0; i < events.length; i += 1) {
458
+ const e = events[i];
459
+ const key = `${e.appId}|${e.stage}`;
460
+ const arr = groups.get(key);
461
+ if (arr)
462
+ arr.push(e);
463
+ else
464
+ groups.set(key, [e]);
465
+ }
466
+ const batchSize = Math.min(200, ((_a = this.opts) == null ? void 0 : _a.batchSize) || 50);
467
+ const entries = Array.from(groups.entries());
468
+ const tasks = [];
469
+ for (let gi = 0; gi < entries.length; gi += 1) {
470
+ const [key, group] = entries[gi];
471
+ group.sort((a, b) => a.seq - b.seq);
472
+ for (let i = 0; i < group.length; i += batchSize) {
473
+ const chunk = group.slice(i, i + batchSize);
474
+ if (chunk.length > 0) {
475
+ const batchId = `batch_${now()}_${Math.random().toString(36).substring(2, 9)}_${key}_${i}`;
476
+ const sendFn = async () => {
477
+ const transporter = this.transporter || await TransportAdapter.getInstance(this.opts).getTransporter();
478
+ this.transporter = transporter;
479
+ await transporter.send(this.generateToken(), {
480
+ appId: chunk[0].appId,
481
+ appStage: chunk[0].stage,
482
+ items: chunk.map((event) => ({
483
+ level: event.level === "FATAL" ? "ERROR" : event.level,
484
+ traceId: event.traceId,
485
+ frontendId: event.frontendId,
486
+ location: event.location,
487
+ message: event.message,
488
+ throwable: event.throwable
489
+ }))
490
+ });
491
+ };
492
+ const task = this.opts.enableRetry && this.retryManager ? this.retryManager.executeWithRetry(batchId, sendFn) : sendFn();
493
+ tasks.push(task);
494
+ }
495
+ }
496
+ }
497
+ await Promise.all(tasks);
498
+ }
499
+ /**
500
+ * 启动批量上报定时器
501
+ */
502
+ startBatchTimer() {
503
+ var _a;
504
+ if (this.batchTimer)
505
+ return;
506
+ this.batchTimer = setInterval(() => {
507
+ var _a2;
508
+ if (!this.closed && this.queueManager && this.queueManager.size() > 0) {
509
+ logDebug(!!((_a2 = this.opts) == null ? void 0 : _a2.debug), "Batch timer triggered, flushing queue");
510
+ this.flush().catch((error) => {
511
+ var _a3;
512
+ logDebug(!!((_a3 = this.opts) == null ? void 0 : _a3.debug), "Batch timer flush failed", error);
513
+ });
514
+ }
515
+ }, this.opts.batchInterval);
516
+ logDebug(!!((_a = this.opts) == null ? void 0 : _a.debug), `Batch timer started with interval ${this.opts.batchInterval}ms`);
517
+ }
518
+ /**
519
+ * 初始化 SDK(注册/初始化)
520
+ */
521
+ initSDK(opts, onSuccess) {
522
+ var _a;
523
+ HttpClient.post(
524
+ getRegisterApi(((_a = this.opts) == null ? void 0 : _a.env) || "develop"),
525
+ {
526
+ appId: opts.appId,
527
+ message: "init",
528
+ appStage: opts.logStage,
529
+ location: "",
530
+ userId: opts.userId,
531
+ storeCode: opts.storeCode
532
+ },
533
+ this.generateToken()
534
+ ).then(async (response) => {
535
+ var _a2;
536
+ logDebug(!!((_a2 = this.opts) == null ? void 0 : _a2.debug), "Register success", response);
537
+ if (!response) {
538
+ return;
539
+ }
540
+ const { type, response: res, error } = response;
541
+ let isSuccess = false;
542
+ let data;
543
+ if (type === "wechat" && (res == null ? void 0 : res.statusCode) === 200) {
544
+ isSuccess = true;
545
+ data = res.data.data;
546
+ }
547
+ if (isSuccess && data) {
548
+ onSuccess(data);
549
+ }
550
+ }).catch((err) => {
551
+ var _a2;
552
+ logDebug(!!((_a2 = this.opts) == null ? void 0 : _a2.debug), "Failed to initialize SDK", err);
553
+ });
554
+ }
555
+ };
556
+ var loggerSDK_default = LoggerSDK;
557
+ export {
558
+ LoggerSDK,
559
+ loggerSDK_default as default
560
+ };
@@ -0,0 +1,101 @@
1
+ // src/core/queueManager.ts
2
+ import { logDebug, safeStringify } from "../utils/tools";
3
+ var QueueManager = class {
4
+ constructor(options) {
5
+ this.queue = [];
6
+ this.opts = options;
7
+ this.storageKey = `${options.storagePrefix}_queue`;
8
+ if (this.opts.enableStorage) {
9
+ this.loadFromStorage();
10
+ }
11
+ }
12
+ /**
13
+ * 添加日志到队列
14
+ */
15
+ enqueue(event) {
16
+ if (this.queue.length >= this.opts.maxSize) {
17
+ this.queue.shift();
18
+ logDebug(!!this.opts.debug, "Queue full, dropped oldest event");
19
+ }
20
+ this.queue.push(event);
21
+ logDebug(!!this.opts.debug, "Enqueued event", event);
22
+ if (this.opts.enableStorage) {
23
+ this.saveToStorage();
24
+ }
25
+ return true;
26
+ }
27
+ /**
28
+ * 批量获取日志(不移除)
29
+ */
30
+ peek(count) {
31
+ return this.queue.slice(0, count);
32
+ }
33
+ /**
34
+ * 批量移除日志
35
+ */
36
+ dequeue(count) {
37
+ const items = this.queue.splice(0, count);
38
+ logDebug(!!this.opts.debug, `Dequeued ${items.length} events`);
39
+ if (this.opts.enableStorage) {
40
+ this.saveToStorage();
41
+ }
42
+ return items;
43
+ }
44
+ /**
45
+ * 获取队列长度
46
+ */
47
+ size() {
48
+ return this.queue.length;
49
+ }
50
+ /**
51
+ * 清空队列
52
+ */
53
+ clear() {
54
+ this.queue = [];
55
+ logDebug(!!this.opts.debug, "Queue cleared");
56
+ if (this.opts.enableStorage) {
57
+ this.removeFromStorage();
58
+ }
59
+ }
60
+ /**
61
+ * 从持久化存储加载队列
62
+ */
63
+ loadFromStorage() {
64
+ try {
65
+ const stored = wx.getStorageSync(this.storageKey);
66
+ if (stored) {
67
+ const parsed = JSON.parse(stored);
68
+ if (Array.isArray(parsed)) {
69
+ this.queue = parsed.slice(0, this.opts.maxSize);
70
+ logDebug(!!this.opts.debug, `Loaded ${this.queue.length} events from storage`);
71
+ }
72
+ }
73
+ } catch (error) {
74
+ logDebug(!!this.opts.debug, "Failed to load queue from storage", error);
75
+ }
76
+ }
77
+ /**
78
+ * 保存队列到持久化存储
79
+ */
80
+ saveToStorage() {
81
+ try {
82
+ const data = safeStringify(this.queue);
83
+ wx.setStorageSync(this.storageKey, data);
84
+ } catch (error) {
85
+ logDebug(!!this.opts.debug, "Failed to save queue to storage", error);
86
+ }
87
+ }
88
+ /**
89
+ * 从持久化存储移除队列
90
+ */
91
+ removeFromStorage() {
92
+ try {
93
+ wx.removeStorageSync(this.storageKey);
94
+ } catch (error) {
95
+ logDebug(!!this.opts.debug, "Failed to remove queue from storage", error);
96
+ }
97
+ }
98
+ };
99
+ export {
100
+ QueueManager
101
+ };