@pluve/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.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @pluve/logger-sdk 使用说明
2
+
3
+ ## 简介
4
+ - 统一的前端日志采集 SDK,支持 H5 与微信小程序。
5
+ - 能力包括:批量上报、分级路由(`info|warn|error`)、自动 PV/错误/性能采集、本地持久化与 IndexedDB 冗余、页面关闭时 `sendBeacon` 兜底。
6
+
7
+ ## 安装与引入
8
+ - 安装:`pnpm add @pluve/logger-sdk`
9
+ - ESM:`import LoggerSDK from '@pluve/logger-sdk'`
10
+ - UMD:构建后全局为 `LoggerSDK`,示例:`const { LoggerSDK } = window.LoggerSDK`
11
+
12
+ ## 快速开始
13
+ ```ts
14
+ import LoggerSDK from '@pluve/logger-sdk';
15
+
16
+ const sdk = new LoggerSDK({
17
+ endpoints: {
18
+ default: '/api/collect',
19
+ info: '/api/collect/info',
20
+ warn: '/api/collect/warn',
21
+ error: '/api/collect/error',
22
+ },
23
+ appId: 'your-app',
24
+ debug: true,
25
+ enableAutoPV: true,
26
+ enablePerf: true,
27
+ });
28
+
29
+ sdk.track({ type: 'custom', level: 'info', ctx: { action: 'click_button' } });
30
+ ```
31
+
32
+ ## API
33
+ - `new LoggerSDK(options)`
34
+ - `track(event, headers?)`:事件入队并持久化,必要时触发批量上报。位置 `packages/logger-sdk/src/loggerSDK.ts:121`
35
+ - `flush(extraHeaders?)`:按批量大小取队列,同批按 `level` 并行上报并重试。位置 `packages/logger-sdk/src/loggerSDK.ts:156-221`
36
+ - `flushAll()`:非阻塞轮询清空队列,尊重并发锁。位置 `packages/logger-sdk/src/loggerSDK.ts:223-233`
37
+ - `identify(user)`:设置应用/用户信息(如 `appId`)。位置 `packages/logger-sdk/src/loggerSDK.ts:222`
38
+ - `setCommon(params)`:动态覆写通用配置(如 `globalHeaders`)。位置 `packages/logger-sdk/src/loggerSDK.ts:227`
39
+ - `destroy()`:停止定时器并关闭。位置 `packages/logger-sdk/src/loggerSDK.ts:232`
40
+
41
+ ## 自动采集
42
+ - 错误采集:JS 错误、未捕获 Promise、资源加载错误。位置 `packages/logger-sdk/src/loggerSDK.ts:238-275`
43
+ - 自动 PV:拦截 `history.pushState/replaceState` 与 `popstate`,采集单页浏览。位置 `packages/logger-sdk/src/loggerSDK.ts:277-299`
44
+ - 性能采集:Navigation Timing + Paint Entries。位置 `packages/logger-sdk/src/loggerSDK.ts:301-328`
45
+
46
+ ## 页面关闭兜底
47
+ - 当页面隐藏或关闭时触发 `flushBeacon()`,使用 `navigator.sendBeacon` 尝试上报队列,不阻塞、不移除队列。
48
+ - 监听位置:`packages/logger-sdk/src/loggerSDK.ts:270-293`
49
+ - 方法位置:`packages/logger-sdk/src/loggerSDK.ts:333-350`
50
+ - 传输适配器 Beacon 通道:`packages/logger-sdk/src/transportAdapter.ts:12-20`
51
+
52
+ ## 配置项(SDKOptions)
53
+ ```ts
54
+ interface SDKOptions {
55
+ endpoints: Partial<Record<'info'|'warn'|'error'|'default', string>>;
56
+ appId?: string;
57
+ env?: 'h5' | 'wechat' | 'unknown';
58
+ batchSize?: number; // 默认 10
59
+ flushInterval?: number; // 默认 5000ms
60
+ retryCount?: number; // 默认 3
61
+ retryBase?: number; // 默认 300ms(指数退避基数)
62
+ storageKey?: string; // 默认 'logger_sdk_cache_v2'
63
+ maxCacheSize?: number; // 默认 2000
64
+ timeout?: number; // 默认 10000ms
65
+ debug?: boolean;
66
+ transport?: (payload: any, opts?: SDKOptions & { endpoint?: string, headers?: Record<string,string> }) => Promise<void>;
67
+ globalHeaders?: Record<string,string>;
68
+ enableAutoPV?: boolean; // 默认 true
69
+ enablePerf?: boolean; // 默认 true
70
+ useBeacon?: boolean; // 传输适配器识别 Beacon 的标志(SDK 内部在 flushBeacon 时传入)
71
+ }
72
+ ```
73
+
74
+ ## 传输适配器
75
+ - 默认选择:`sendBeacon`(兜底)→ 微信 `wx.request` → 浏览器 `fetch`。
76
+ - 可自定义:实现 `transport(payload, opts)`,识别 `opts.useBeacon` 后走轻量通道。
77
+
78
+ ## 持久化与冗余
79
+ - H5:`localStorage` + IndexedDB 冗余队列。初始化位置 `packages/logger-sdk/src/loggerSDK.ts:50-61`
80
+ - 微信:`wx.*Storage`。实现位置 `packages/logger-sdk/src/storeAdapter.ts`
81
+
82
+ ## 最佳实践
83
+ - 分级路由:至少提供 `default`,按需配置 `info|warn|error` 的独立地址。
84
+ - 吞吐与可靠性:高频场景提高 `batchSize`(如 20–50),缩短 `flushInterval`(如 3000–5000ms);弱网提高 `retryCount` 或 `retryBase`。
85
+ - 统一头部:在 `globalHeaders` 添加溯源头(如 `x-trace-id`、`x-app-version`)。
86
+ - 页面关闭兜底:无需手动调用,SDK 已自动在合适时机触发;若使用自定义 `transport`,注意识别 `useBeacon`。
87
+
88
+ ## 示例:自定义传输
89
+ ```ts
90
+ const transport = async (payload: any, opts?: any) => {
91
+ const body = JSON.stringify(payload);
92
+ if (opts?.useBeacon && navigator.sendBeacon) {
93
+ navigator.sendBeacon(opts.endpoint || '', new Blob([body], { type: 'application/json' }));
94
+ return;
95
+ }
96
+ await fetch(opts?.endpoint || '', {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json', ...(opts?.globalHeaders || {}), ...(opts?.headers || {}) },
99
+ body,
100
+ });
101
+ };
102
+
103
+ const sdk = new LoggerSDK({ endpoints: { default: '/api/collect' }, transport });
104
+ ```
105
+
106
+ ## 调试
107
+ - 开启 `debug: true` 查看控制台内部日志。
108
+ - 生成 UMD Demo HTML:`LoggerSDK.generateDemoHtml()`(方法位置:`packages/logger-sdk/src/loggerSDK.ts:330-361`)。
@@ -0,0 +1,10 @@
1
+ export default class IDBQueue {
2
+ private dbName;
3
+ private storeName;
4
+ private db;
5
+ constructor(dbName?: string, storeName?: string);
6
+ open(): Promise<void>;
7
+ add(item: any): Promise<void>;
8
+ getAll(): Promise<any[]>;
9
+ clear(): Promise<unknown>;
10
+ }
@@ -0,0 +1,88 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/dbQueue.ts
20
+ var dbQueue_exports = {};
21
+ __export(dbQueue_exports, {
22
+ default: () => IDBQueue
23
+ });
24
+ module.exports = __toCommonJS(dbQueue_exports);
25
+ var import_utils = require("./utils");
26
+ var IDBQueue = class {
27
+ constructor(dbName = "logger_sdk_db", storeName = "queue") {
28
+ this.db = null;
29
+ this.dbName = dbName;
30
+ this.storeName = storeName;
31
+ }
32
+ // 打开数据库并初始化对象仓库
33
+ async open() {
34
+ if (!(0, import_utils.isBrowser)() || !(0, import_utils.isIndexedDBAvailable)())
35
+ return;
36
+ if (this.db)
37
+ return;
38
+ return new Promise((resolve, reject) => {
39
+ const req = indexedDB.open(this.dbName, 1);
40
+ req.onupgradeneeded = () => {
41
+ const db = req.result;
42
+ if (!db.objectStoreNames.contains(this.storeName))
43
+ db.createObjectStore(this.storeName, { autoIncrement: true });
44
+ };
45
+ req.onsuccess = () => {
46
+ this.db = req.result;
47
+ resolve();
48
+ };
49
+ req.onerror = () => reject(req.error);
50
+ });
51
+ }
52
+ // 入队:追加一条记录
53
+ async add(item) {
54
+ if (!this.db)
55
+ return;
56
+ return new Promise((res, rej) => {
57
+ const tx = this.db.transaction(this.storeName, "readwrite");
58
+ const st = tx.objectStore(this.storeName);
59
+ const r = st.add(item);
60
+ r.onsuccess = () => res();
61
+ r.onerror = () => rej(r.error);
62
+ });
63
+ }
64
+ // 读取全部记录(调试/回溯用)
65
+ async getAll() {
66
+ if (!this.db)
67
+ return [];
68
+ return new Promise((res, rej) => {
69
+ const tx = this.db.transaction(this.storeName, "readonly");
70
+ const st = tx.objectStore(this.storeName);
71
+ const req = st.getAll();
72
+ req.onsuccess = () => res(req.result || []);
73
+ req.onerror = () => rej(req.error);
74
+ });
75
+ }
76
+ // 清空队列:发送成功后用于兜底清理
77
+ async clear() {
78
+ if (!this.db)
79
+ return;
80
+ return new Promise((res, rej) => {
81
+ const tx = this.db.transaction(this.storeName, "readwrite");
82
+ const st = tx.objectStore(this.storeName);
83
+ const req = st.clear();
84
+ req.onsuccess = () => res(void 0);
85
+ req.onerror = () => rej(req.error);
86
+ });
87
+ }
88
+ };
@@ -0,0 +1 @@
1
+ export { LoggerSDK } from './loggerSDK';
@@ -0,0 +1,29 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.ts
20
+ var src_exports = {};
21
+ __export(src_exports, {
22
+ LoggerSDK: () => import_loggerSDK.LoggerSDK
23
+ });
24
+ module.exports = __toCommonJS(src_exports);
25
+ var import_loggerSDK = require("./loggerSDK");
26
+ // Annotate the CommonJS export names for ESM import in node:
27
+ 0 && (module.exports = {
28
+ LoggerSDK
29
+ });
@@ -0,0 +1,29 @@
1
+ import { LogEvent, SDKOptions } from './types';
2
+ export declare class LoggerSDK {
3
+ private opts;
4
+ private env;
5
+ private inMemoryQueue;
6
+ private seq;
7
+ private timerId;
8
+ private storage;
9
+ private closed;
10
+ private idbQueue;
11
+ private flushing;
12
+ constructor(options: SDKOptions);
13
+ private logDebug;
14
+ private loadFromStorage;
15
+ private persistToStorage;
16
+ private startTimer;
17
+ private stopTimer;
18
+ track(event: Partial<LogEvent>, headers?: Record<string, string>): Promise<void>;
19
+ flush(extraHeaders?: Record<string, string>): Promise<void>;
20
+ flushAll(): Promise<void>;
21
+ identify(user: Record<string, any>): Promise<void>;
22
+ setCommon(params: Record<string, any>): Promise<void>;
23
+ destroy(): void;
24
+ private attachGlobalHandlers;
25
+ private installAutoPV;
26
+ private collectPerf;
27
+ private flushBeacon;
28
+ }
29
+ export default LoggerSDK;
@@ -0,0 +1,426 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/loggerSDK.ts
30
+ var loggerSDK_exports = {};
31
+ __export(loggerSDK_exports, {
32
+ LoggerSDK: () => LoggerSDK,
33
+ default: () => loggerSDK_default
34
+ });
35
+ module.exports = __toCommonJS(loggerSDK_exports);
36
+ var import_dbQueue = __toESM(require("./dbQueue"));
37
+ var import_transportAdapter = require("./transportAdapter");
38
+ var import_utils = require("./utils");
39
+ var import_storeAdapter = require("./storeAdapter");
40
+ var LoggerSDK = class {
41
+ constructor(options) {
42
+ this.inMemoryQueue = [];
43
+ this.seq = 0;
44
+ this.timerId = null;
45
+ this.storage = null;
46
+ this.closed = false;
47
+ this.idbQueue = null;
48
+ this.flushing = false;
49
+ const defaults = {
50
+ endpoints: options.endpoints || {},
51
+ appId: options.appId || "",
52
+ env: options.env || (() => {
53
+ if ((0, import_utils.isWeChatMiniProgram)())
54
+ return "wechat";
55
+ if ((0, import_utils.isBrowser)())
56
+ return "h5";
57
+ return "unknown";
58
+ })(),
59
+ batchSize: options.batchSize || 10,
60
+ flushInterval: options.flushInterval || 5e3,
61
+ retryCount: options.retryCount || 3,
62
+ retryBase: options.retryBase || 300,
63
+ storageKey: options.storageKey || "logger_sdk_cache_v2",
64
+ maxCacheSize: options.maxCacheSize || 2e3,
65
+ timeout: options.timeout || 1e4,
66
+ debug: !!options.debug,
67
+ transport: options.transport || import_transportAdapter.defaultTransport,
68
+ globalHeaders: options.globalHeaders || {},
69
+ enableAutoPV: options.enableAutoPV !== false,
70
+ enablePerf: options.enablePerf !== false,
71
+ usePixel: options.usePixel || false,
72
+ pixelParam: options.pixelParam || "data",
73
+ maxPixelUrlLen: options.maxPixelUrlLen || 1900
74
+ };
75
+ this.opts = Object.assign(defaults, options);
76
+ this.env = this.opts.env;
77
+ if (this.env === "wechat")
78
+ this.storage = (0, import_storeAdapter.wechatStorage)(this.opts.storageKey);
79
+ else if (this.env === "h5")
80
+ this.storage = (0, import_storeAdapter.browserStorage)(this.opts.storageKey);
81
+ else
82
+ this.storage = null;
83
+ if ((0, import_utils.isBrowser)() && (0, import_utils.isIndexedDBAvailable)()) {
84
+ this.idbQueue = new import_dbQueue.default("logger_sdk_db", "queue");
85
+ this.idbQueue.open().catch(() => {
86
+ this.idbQueue = null;
87
+ });
88
+ }
89
+ this.loadFromStorage().then(() => {
90
+ if (this.opts.flushInterval > 0)
91
+ this.startTimer();
92
+ });
93
+ this.attachGlobalHandlers();
94
+ if (this.opts.enableAutoPV && this.env === "h5")
95
+ this.installAutoPV();
96
+ if (this.opts.enablePerf && this.env === "h5") {
97
+ if (document.readyState === "complete")
98
+ this.collectPerf();
99
+ else
100
+ window.addEventListener("load", () => this.collectPerf());
101
+ }
102
+ }
103
+ logDebug(...args) {
104
+ if (this.opts.debug)
105
+ console.debug("[LoggerSDK]", ...args);
106
+ }
107
+ async loadFromStorage() {
108
+ if (!this.storage)
109
+ return;
110
+ try {
111
+ const raw = await this.storage.get();
112
+ if (raw) {
113
+ const arr = JSON.parse(raw);
114
+ this.inMemoryQueue = arr.concat(this.inMemoryQueue).slice(-this.opts.maxCacheSize);
115
+ this.logDebug("loaded persisted queue", this.inMemoryQueue.length);
116
+ }
117
+ } catch (e) {
118
+ this.logDebug("load persisted fail", e);
119
+ }
120
+ }
121
+ async persistToStorage() {
122
+ if (!this.storage)
123
+ return;
124
+ try {
125
+ await this.storage.set((0, import_utils.safeStringify)(this.inMemoryQueue));
126
+ this.logDebug("persisted queue", this.inMemoryQueue.length);
127
+ } catch (e) {
128
+ this.logDebug("persist fail", e);
129
+ }
130
+ }
131
+ startTimer() {
132
+ if (this.timerId)
133
+ return;
134
+ this.timerId = setInterval(() => this.flush().catch(() => {
135
+ }), this.opts.flushInterval);
136
+ }
137
+ stopTimer() {
138
+ if (!this.timerId)
139
+ return;
140
+ clearInterval(this.timerId);
141
+ this.timerId = null;
142
+ }
143
+ // 将事件入队(先到内存,再持久化)
144
+ async track(event, headers) {
145
+ if (this.closed)
146
+ return;
147
+ this.seq += 1;
148
+ const e = {
149
+ type: event.type || "custom",
150
+ time: event.time || (0, import_utils.now)(),
151
+ user: event.user || void 0,
152
+ ctx: event.ctx || {},
153
+ level: event.level || "info",
154
+ seq: this.seq
155
+ };
156
+ this.inMemoryQueue.push(e);
157
+ this.logDebug("enqueue", e);
158
+ await this.persistToStorage();
159
+ if (this.idbQueue) {
160
+ try {
161
+ await this.idbQueue.add(e);
162
+ } catch (err) {
163
+ this.logDebug("idb add fail", err);
164
+ }
165
+ }
166
+ if (this.inMemoryQueue.length >= this.opts.batchSize) {
167
+ await this.flush(headers).catch(() => {
168
+ });
169
+ }
170
+ }
171
+ // flush:从队列中取一批发送(按 level 分 endpoint,支持部分成功剔除)
172
+ async flush(extraHeaders) {
173
+ if (this.closed)
174
+ return;
175
+ if (this.inMemoryQueue.length === 0)
176
+ return;
177
+ if (this.flushing)
178
+ return;
179
+ this.flushing = true;
180
+ try {
181
+ const batch = this.inMemoryQueue.slice(0, this.opts.batchSize);
182
+ const groups = {};
183
+ batch.forEach((ev) => {
184
+ const level = ev.level || "info";
185
+ (groups[level] = groups[level] || []).push(ev);
186
+ });
187
+ const delay = (ms) => new Promise((r) => setTimeout(r, ms));
188
+ const sendGroup = (level, events) => {
189
+ const endpoint = this.opts.endpoints[level] || this.opts.endpoints.default;
190
+ const payload = { appId: this.opts.appId, env: this.env, ts: (0, import_utils.now)(), level, events };
191
+ const maxTry = this.opts.retryCount;
192
+ const attemptOnce = (attempt) => {
193
+ const transportOpts = { ...this.opts, endpoint, headers: { ...this.opts.globalHeaders || {}, ...extraHeaders || {} } };
194
+ return this.opts.transport(payload, transportOpts).then(() => true).catch(async (err) => {
195
+ const next = attempt + 1;
196
+ this.logDebug("send fail", level, next, err);
197
+ if (next > maxTry)
198
+ return false;
199
+ const wait = (this.opts.retryBase || 300) * 2 ** (next - 1);
200
+ await delay(wait);
201
+ return attemptOnce(next);
202
+ });
203
+ };
204
+ return attemptOnce(0).then((ok) => ({ ok, level, events }));
205
+ };
206
+ const results = await Promise.all(Object.keys(groups).map((level) => sendGroup(level, groups[level])));
207
+ const successLevels = new Set(results.filter((r) => r.ok).map((r) => r.level));
208
+ const allOk = results.length > 0 && results.every((r) => r.ok);
209
+ if (!allOk && successLevels.size === 0) {
210
+ this.logDebug("batch send failed, keep batch in queue");
211
+ return;
212
+ }
213
+ const removeSeq = new Set(batch.filter((e) => successLevels.has(e.level || "info")).map((e) => e.seq));
214
+ this.inMemoryQueue = this.inMemoryQueue.filter((x) => !removeSeq.has(x.seq));
215
+ await this.persistToStorage();
216
+ if (allOk && this.idbQueue) {
217
+ try {
218
+ await this.idbQueue.clear();
219
+ } catch (e) {
220
+ this.logDebug("idb clear fail", e);
221
+ }
222
+ }
223
+ } finally {
224
+ this.flushing = false;
225
+ }
226
+ }
227
+ // 将队列尽力全部发送(分批轮询 + 安全阈值)
228
+ async flushAll() {
229
+ let safety = 0;
230
+ return new Promise((resolve) => {
231
+ const tick = async () => {
232
+ safety += 1;
233
+ if (this.inMemoryQueue.length === 0 || safety >= 200)
234
+ return resolve();
235
+ if (!this.flushing) {
236
+ try {
237
+ await this.flush();
238
+ } catch {
239
+ }
240
+ }
241
+ setTimeout(tick, 50);
242
+ };
243
+ tick();
244
+ });
245
+ }
246
+ async identify(user) {
247
+ this.opts.appId = user && user.appId || this.opts.appId;
248
+ this.logDebug("identify", user);
249
+ }
250
+ async setCommon(params) {
251
+ Object.assign(this.opts, params);
252
+ this.logDebug("setCommon", params);
253
+ }
254
+ destroy() {
255
+ this.stopTimer();
256
+ this.closed = true;
257
+ }
258
+ // ========== 自动采集与错误处理 ===========
259
+ attachGlobalHandlers() {
260
+ if (this.env === "h5" && typeof window !== "undefined") {
261
+ const win = window;
262
+ win.addEventListener && win.addEventListener("error", (ev) => {
263
+ try {
264
+ this.track({ type: "error", level: "error", ctx: { message: ev.message, filename: ev.filename, lineno: ev.lineno, colno: ev.colno, stack: ev.error && ev.error.stack } });
265
+ } catch {
266
+ }
267
+ });
268
+ win.addEventListener && win.addEventListener("unhandledrejection", (ev) => {
269
+ try {
270
+ this.track({ type: "error", level: "error", ctx: { reason: ev.reason && (ev.reason.stack || ev.reason) } });
271
+ } catch {
272
+ }
273
+ });
274
+ win.addEventListener && win.addEventListener(
275
+ "error",
276
+ (ev) => {
277
+ if (ev.target && (ev.target.src || ev.target.href)) {
278
+ try {
279
+ this.track({ type: "error", level: "warn", ctx: { resource: ev.target.src || ev.target.href, tag: ev.target.tagName } });
280
+ } catch {
281
+ }
282
+ }
283
+ },
284
+ true
285
+ );
286
+ document.addEventListener && document.addEventListener("visibilitychange", () => {
287
+ try {
288
+ if (document.visibilityState === "hidden")
289
+ this.flushBeacon();
290
+ } catch {
291
+ }
292
+ });
293
+ win.addEventListener && win.addEventListener("pagehide", () => {
294
+ try {
295
+ this.flushBeacon();
296
+ } catch {
297
+ }
298
+ });
299
+ win.addEventListener && win.addEventListener("beforeunload", () => {
300
+ try {
301
+ this.flushBeacon();
302
+ } catch {
303
+ }
304
+ });
305
+ }
306
+ }
307
+ // 自动 PV(single-page 支持简单的 history 池监听)
308
+ installAutoPV() {
309
+ if (!(0, import_utils.isBrowser)())
310
+ return;
311
+ const { history, location } = window;
312
+ const { pushState } = history;
313
+ const { replaceState } = history;
314
+ const onUrlChange = () => {
315
+ try {
316
+ this.track({ type: "pageview", level: "info", ctx: { path: location.pathname + location.search, title: document.title } });
317
+ } catch {
318
+ }
319
+ };
320
+ history.pushState = function(...args) {
321
+ pushState.apply(this, args);
322
+ window.dispatchEvent(new Event("locationchange"));
323
+ };
324
+ history.replaceState = function(...args) {
325
+ replaceState.apply(this, args);
326
+ window.dispatchEvent(new Event("locationchange"));
327
+ };
328
+ window.addEventListener("popstate", () => window.dispatchEvent(new Event("locationchange")));
329
+ window.addEventListener("locationchange", onUrlChange);
330
+ onUrlChange();
331
+ }
332
+ // 性能采集(Navigation timing + Paint)
333
+ collectPerf() {
334
+ if (!(0, import_utils.isBrowser)() || !("performance" in window))
335
+ return;
336
+ try {
337
+ const perf = window.performance;
338
+ const nav = perf.getEntriesByType && perf.getEntriesByType("navigation") && perf.getEntriesByType("navigation")[0];
339
+ const paints = perf.getEntriesByType ? perf.getEntriesByType("paint") : [];
340
+ const data = {};
341
+ if (nav) {
342
+ data.ttfb = nav.responseStart - nav.requestStart;
343
+ data.domContentLoaded = nav.domContentLoadedEventEnd - nav.startTime;
344
+ data.load = nav.loadEventEnd - nav.startTime;
345
+ } else if (perf.timing) {
346
+ const t = perf.timing;
347
+ data.ttfb = t.responseStart - t.requestStart;
348
+ data.domContentLoaded = t.domContentLoadedEventEnd - t.navigationStart;
349
+ data.load = t.loadEventEnd - t.navigationStart;
350
+ }
351
+ if (paints && paints.length) {
352
+ paints.forEach((p) => {
353
+ data[p.name] = p.startTime;
354
+ });
355
+ }
356
+ this.track({ type: "perf", level: "info", ctx: data });
357
+ } catch (e) {
358
+ this.logDebug("collect perf fail", e);
359
+ }
360
+ }
361
+ flushBeacon() {
362
+ if (!(0, import_utils.isBrowser)())
363
+ return;
364
+ if (this.inMemoryQueue.length === 0)
365
+ return;
366
+ const groups = {};
367
+ this.inMemoryQueue.forEach((ev) => {
368
+ const level = ev.level || "info";
369
+ (groups[level] = groups[level] || []).push(ev);
370
+ });
371
+ Object.keys(groups).forEach((level) => {
372
+ const events = groups[level];
373
+ const endpoint = this.opts.endpoints[level] || this.opts.endpoints.default;
374
+ const payload = { appId: this.opts.appId, env: this.env, ts: (0, import_utils.now)(), level, events };
375
+ const transportOpts = {
376
+ ...this.opts,
377
+ endpoint,
378
+ headers: { ...this.opts.globalHeaders || {} },
379
+ useBeacon: true,
380
+ usePixel: this.opts.usePixel,
381
+ pixelParam: this.opts.pixelParam,
382
+ maxPixelUrlLen: this.opts.maxPixelUrlLen
383
+ };
384
+ try {
385
+ this.opts.transport(payload, transportOpts);
386
+ } catch {
387
+ }
388
+ });
389
+ }
390
+ // 生成简单 Demo 页面(HTML 字符串),方便生成 demo 文档站点
391
+ // public generateDemoHtml(opts?: { scriptUrl?: string }) {
392
+ // const scriptUrl = (opts && opts.scriptUrl) || 'dist/index.umd.js';
393
+ // const html = ```
394
+ // <!doctype html>
395
+ // <html>
396
+ // <head>
397
+ // <meta charset="utf-8" />
398
+ // <title>LoggerSDK Demo</title>
399
+ // </head>
400
+ // <body>
401
+ // <h1>LoggerSDK Demo</h1>
402
+ // <script src="${scriptUrl}"></script>
403
+ // <script>
404
+ // // 通过 UMD 全局 MyLogger 使用
405
+ // const sdk = new MyLogger.LoggerSDK({
406
+ // endpoints: { default: '/api/collect', info: '/api/collect/info', error: '/api/collect/error' },
407
+ // appId: 'demo-app',
408
+ // debug: true,
409
+ // enableAutoPV: true,
410
+ // enablePerf: true
411
+ // });
412
+ // // 手动埋点
413
+ // sdk.track({ type: 'custom', level: 'info', ctx: { action: 'click_demo' } });
414
+ // // 模拟错误
415
+ // setTimeout(() => { throw new Error('demo error'); }, 2000);
416
+ // </script>
417
+ // </body>
418
+ // </html>```;
419
+ // return html;
420
+ // }
421
+ };
422
+ var loggerSDK_default = LoggerSDK;
423
+ // Annotate the CommonJS export names for ESM import in node:
424
+ 0 && (module.exports = {
425
+ LoggerSDK
426
+ });