@aigc-kino/logger-sdk 1.3.0

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.cjs ADDED
@@ -0,0 +1,841 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ LogType: () => LogType,
24
+ Logger: () => Logger,
25
+ VuePlugin: () => VuePlugin,
26
+ attachAxios: () => attachAxios,
27
+ captureError: () => captureError,
28
+ captureMessage: () => captureMessage,
29
+ clearUser: () => clearUser,
30
+ destroy: () => destroy,
31
+ flush: () => flush,
32
+ getSessionId: () => getSessionId,
33
+ getTraceId: () => getTraceId,
34
+ init: () => init,
35
+ scrubUrl: () => scrubUrl,
36
+ setTag: () => setTag,
37
+ setUser: () => setUser,
38
+ track: () => track
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/types/log-types.ts
43
+ var LogType = /* @__PURE__ */ ((LogType2) => {
44
+ LogType2["JsError"] = "js-error";
45
+ LogType2["PromiseError"] = "promise-error";
46
+ LogType2["VueError"] = "vue-error";
47
+ LogType2["Network"] = "network";
48
+ LogType2["Resource"] = "resource";
49
+ LogType2["Performance"] = "performance";
50
+ LogType2["Behavior"] = "behavior";
51
+ LogType2["Page"] = "page";
52
+ LogType2["Business"] = "business";
53
+ LogType2["Console"] = "console";
54
+ LogType2["Custom"] = "custom";
55
+ return LogType2;
56
+ })(LogType || {});
57
+
58
+ // src/storage.ts
59
+ var DB_NAME = "kino-log-platfrom-logger-sdk";
60
+ var STORE = "pending-logs";
61
+ var MAX_CACHE = 2e3;
62
+ function openDb() {
63
+ return new Promise((resolve) => {
64
+ if (typeof indexedDB === "undefined") return resolve(null);
65
+ const req = indexedDB.open(DB_NAME, 1);
66
+ req.onupgradeneeded = () => {
67
+ if (!req.result.objectStoreNames.contains(STORE)) {
68
+ req.result.createObjectStore(STORE, { autoIncrement: true });
69
+ }
70
+ };
71
+ req.onsuccess = () => resolve(req.result);
72
+ req.onerror = () => resolve(null);
73
+ });
74
+ }
75
+ var LogStorage = class {
76
+ constructor() {
77
+ this.dbPromise = openDb();
78
+ }
79
+ async save(logs) {
80
+ const db = await this.dbPromise;
81
+ if (!db || logs.length === 0) return;
82
+ await new Promise((resolve) => {
83
+ const tx = db.transaction(STORE, "readwrite");
84
+ const store = tx.objectStore(STORE);
85
+ for (const log of logs) store.add(log);
86
+ tx.oncomplete = () => resolve();
87
+ tx.onerror = () => resolve();
88
+ });
89
+ await this.trim();
90
+ }
91
+ async drain() {
92
+ const db = await this.dbPromise;
93
+ if (!db) return [];
94
+ return new Promise((resolve) => {
95
+ const tx = db.transaction(STORE, "readwrite");
96
+ const store = tx.objectStore(STORE);
97
+ const req = store.getAll();
98
+ req.onsuccess = () => {
99
+ const logs = req.result ?? [];
100
+ store.clear();
101
+ resolve(logs);
102
+ };
103
+ req.onerror = () => resolve([]);
104
+ });
105
+ }
106
+ async trim() {
107
+ const db = await this.dbPromise;
108
+ if (!db) return;
109
+ await new Promise((resolve) => {
110
+ const tx = db.transaction(STORE, "readwrite");
111
+ const store = tx.objectStore(STORE);
112
+ const countReq = store.count();
113
+ countReq.onsuccess = () => {
114
+ let excess = countReq.result - MAX_CACHE;
115
+ if (excess <= 0) return resolve();
116
+ const cursorReq = store.openCursor();
117
+ cursorReq.onsuccess = () => {
118
+ const cursor = cursorReq.result;
119
+ if (cursor && excess > 0) {
120
+ cursor.delete();
121
+ excess--;
122
+ cursor.continue();
123
+ } else {
124
+ resolve();
125
+ }
126
+ };
127
+ cursorReq.onerror = () => resolve();
128
+ };
129
+ countReq.onerror = () => resolve();
130
+ });
131
+ }
132
+ };
133
+
134
+ // src/transport.ts
135
+ var Transport = class {
136
+ constructor(options) {
137
+ this.options = options;
138
+ this.queue = [];
139
+ this.storage = new LogStorage();
140
+ this.timer = null;
141
+ this.uploading = false;
142
+ this.timer = setInterval(() => void this.flush(), options.uploadInterval);
143
+ window.addEventListener("online", () => void this.flush());
144
+ document.addEventListener("visibilitychange", () => {
145
+ if (document.visibilityState === "hidden") this.flushWithBeacon();
146
+ });
147
+ window.addEventListener("pagehide", () => this.flushWithBeacon());
148
+ void this.recover();
149
+ }
150
+ get uploadUrl() {
151
+ return `${this.options.serverUrl.replace(/\/$/, "")}/api/log/upload?token=${encodeURIComponent(this.options.token)}`;
152
+ }
153
+ add(log) {
154
+ this.queue.push(log);
155
+ if (this.queue.length >= this.options.batchSize) void this.flush();
156
+ }
157
+ async flush() {
158
+ if (this.uploading || this.queue.length === 0) return;
159
+ const batch = this.queue.splice(0, this.options.batchSize);
160
+ this.uploading = true;
161
+ try {
162
+ const ok = await this.send(batch);
163
+ if (!ok) await this.storage.save(batch);
164
+ } finally {
165
+ this.uploading = false;
166
+ }
167
+ if (this.queue.length >= this.options.batchSize) void this.flush();
168
+ }
169
+ flushWithBeacon() {
170
+ if (this.queue.length === 0) return;
171
+ const batch = this.queue.splice(0, this.queue.length);
172
+ const payload = { projectId: this.options.projectId, logs: batch };
173
+ const body = JSON.stringify(payload);
174
+ let sent = false;
175
+ if (navigator.sendBeacon) {
176
+ sent = navigator.sendBeacon(this.uploadUrl, new Blob([body], { type: "application/json" }));
177
+ }
178
+ if (!sent) {
179
+ void fetch(this.uploadUrl, {
180
+ method: "POST",
181
+ headers: { "Content-Type": "application/json" },
182
+ body,
183
+ keepalive: true
184
+ }).catch(() => void this.storage.save(batch));
185
+ }
186
+ }
187
+ destroy() {
188
+ if (this.timer) clearInterval(this.timer);
189
+ this.flushWithBeacon();
190
+ }
191
+ async recover() {
192
+ const cached = await this.storage.drain();
193
+ if (cached.length > 0) this.queue.unshift(...cached);
194
+ }
195
+ async send(logs, attempt = 0) {
196
+ const payload = { projectId: this.options.projectId, logs };
197
+ try {
198
+ const res = await fetch(this.uploadUrl, {
199
+ method: "POST",
200
+ headers: { "Content-Type": "application/json" },
201
+ body: JSON.stringify(payload),
202
+ keepalive: true
203
+ });
204
+ if (res.ok) return true;
205
+ if (res.status >= 400 && res.status < 500) return true;
206
+ } catch {
207
+ }
208
+ if (attempt < this.options.maxRetry) {
209
+ await new Promise((r) => setTimeout(r, 1e3 * 2 ** attempt));
210
+ return this.send(logs, attempt + 1);
211
+ }
212
+ return false;
213
+ }
214
+ };
215
+
216
+ // src/utils.ts
217
+ function uuid() {
218
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
219
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
220
+ const r = Math.random() * 16 | 0;
221
+ const v = c === "x" ? r : r & 3 | 8;
222
+ return v.toString(16);
223
+ });
224
+ }
225
+ function getBrowser(ua) {
226
+ if (/Edg\//.test(ua)) return "Edge";
227
+ if (/Chrome\//.test(ua)) return "Chrome";
228
+ if (/Firefox\//.test(ua)) return "Firefox";
229
+ if (/Safari\//.test(ua)) return "Safari";
230
+ return "Unknown";
231
+ }
232
+ function getDevice(ua) {
233
+ return /Mobile|Android|iPhone|iPad/.test(ua) ? "Mobile" : "Desktop";
234
+ }
235
+ function getNetworkType() {
236
+ const nav = navigator;
237
+ return nav.connection?.effectiveType ?? "unknown";
238
+ }
239
+ function matchAny(value, patterns) {
240
+ return patterns.some((p) => typeof p === "string" ? value.includes(p) : p.test(value));
241
+ }
242
+ var SENSITIVE_PARAM_RE = /^(authorization|token|password|passwd|pwd|secret|cookie|phone|mobile|idcard|id-card)$/i;
243
+ function maskSensitiveParams(params) {
244
+ const sensitiveKeys = [];
245
+ params.forEach((_value, key) => {
246
+ if (SENSITIVE_PARAM_RE.test(key)) sensitiveKeys.push(key);
247
+ });
248
+ for (const key of sensitiveKeys) params.set(key, "***");
249
+ }
250
+ function scrubUrl(url) {
251
+ try {
252
+ const u = new URL(url, location.href);
253
+ maskSensitiveParams(u.searchParams);
254
+ const qi = u.hash.indexOf("?");
255
+ if (qi >= 0) {
256
+ const hashParams = new URLSearchParams(u.hash.slice(qi + 1));
257
+ maskSensitiveParams(hashParams);
258
+ u.hash = `${u.hash.slice(0, qi)}?${hashParams.toString()}`;
259
+ }
260
+ return u.toString();
261
+ } catch {
262
+ return url;
263
+ }
264
+ }
265
+
266
+ // src/core/logger.ts
267
+ var SESSION_KEY = "logger-sdk-session-id";
268
+ var Logger = class {
269
+ constructor(options) {
270
+ this.tags = {};
271
+ this.cleanups = [];
272
+ this.destroyed = false;
273
+ this.options = {
274
+ env: "production",
275
+ version: "0.0.0",
276
+ enableError: true,
277
+ enablePerformance: true,
278
+ enableBehavior: true,
279
+ enableNetwork: true,
280
+ enableConsole: false,
281
+ sampleRate: 1,
282
+ batchSize: 50,
283
+ uploadInterval: 5e3,
284
+ maxRetry: 3,
285
+ ignoreErrors: [],
286
+ ignoreUrls: [],
287
+ remoteConfig: true,
288
+ debug: false,
289
+ ...options
290
+ };
291
+ this.sessionId = this.resolveSessionId();
292
+ this.traceId = this.resolveTraceId();
293
+ this.transport = new Transport(this.options);
294
+ if (this.options.remoteConfig) void this.pullRemoteConfig();
295
+ }
296
+ isUploadUrl(url) {
297
+ return url.includes("/api/log/upload") || url.includes("/api/users/profile");
298
+ }
299
+ shouldIgnoreUrl(url) {
300
+ return this.isUploadUrl(url) || matchAny(url, this.options.ignoreUrls);
301
+ }
302
+ setUser(userId, profile) {
303
+ this.options.userId = userId;
304
+ if (profile !== void 0) {
305
+ void this.uploadUserProfile(userId, profile).catch((error) => {
306
+ if (this.options.debug) console.warn("[logger-sdk] user profile upload failed", error);
307
+ });
308
+ }
309
+ }
310
+ /** 退出登录时调用,解除当前用户关联,后续日志不再携带 userId */
311
+ clearUser() {
312
+ this.options.userId = void 0;
313
+ }
314
+ setTag(key, value) {
315
+ this.tags[key] = value;
316
+ }
317
+ report(raw) {
318
+ if (this.destroyed) return;
319
+ if (Math.random() > this.options.sampleRate) return;
320
+ if (raw.message && matchAny(raw.message, this.options.ignoreErrors)) return;
321
+ const ua = navigator.userAgent;
322
+ let log = {
323
+ projectId: this.options.projectId,
324
+ env: this.options.env,
325
+ version: this.options.version,
326
+ type: raw.type,
327
+ level: raw.level,
328
+ message: String(raw.message).slice(0, 5e3),
329
+ stack: raw.stack?.slice(0, 1e4),
330
+ url: location.href,
331
+ userId: this.options.userId,
332
+ sessionId: this.sessionId,
333
+ traceId: this.traceId,
334
+ browser: getBrowser(ua),
335
+ device: getDevice(ua),
336
+ network: getNetworkType(),
337
+ timestamp: Date.now(),
338
+ extra: { ...this.tags, ...raw.extra },
339
+ ...this.options.microFrontend,
340
+ route: this.options.microFrontend ? location.pathname : void 0
341
+ };
342
+ if (this.options.beforeSend) {
343
+ const result = this.options.beforeSend(log);
344
+ if (result === false) return;
345
+ log = result;
346
+ }
347
+ this.transport.add(log);
348
+ }
349
+ track(event, data) {
350
+ this.report({ type: "business" /* Business */, level: "info", message: event, extra: data });
351
+ }
352
+ captureError(error, extra) {
353
+ const err = error instanceof Error ? error : new Error(String(error));
354
+ this.report({ type: "js-error" /* JsError */, level: "error", message: err.message, stack: err.stack, extra });
355
+ }
356
+ captureMessage(message, level = "info") {
357
+ this.report({ type: "custom" /* Custom */, level, message });
358
+ }
359
+ flush() {
360
+ return this.transport.flush();
361
+ }
362
+ addCleanup(fn) {
363
+ this.cleanups.push(fn);
364
+ }
365
+ destroy() {
366
+ this.destroyed = true;
367
+ for (const fn of this.cleanups) fn();
368
+ this.cleanups = [];
369
+ this.transport.destroy();
370
+ }
371
+ resolveSessionId() {
372
+ try {
373
+ let id = sessionStorage.getItem(SESSION_KEY);
374
+ if (!id) {
375
+ id = uuid();
376
+ sessionStorage.setItem(SESSION_KEY, id);
377
+ }
378
+ return id;
379
+ } catch {
380
+ return uuid();
381
+ }
382
+ }
383
+ resolveTraceId() {
384
+ const w = window;
385
+ if (!w.__LOGGER_TRACE_ID__) w.__LOGGER_TRACE_ID__ = uuid();
386
+ return w.__LOGGER_TRACE_ID__;
387
+ }
388
+ async pullRemoteConfig() {
389
+ try {
390
+ const base = this.options.serverUrl.replace(/\/$/, "");
391
+ const res = await fetch(
392
+ `${base}/api/sdk/config?projectId=${encodeURIComponent(this.options.projectId)}&token=${encodeURIComponent(this.options.token)}`
393
+ );
394
+ if (!res.ok) return;
395
+ const config = await res.json();
396
+ if (typeof config.sampleRate === "number") this.options.sampleRate = config.sampleRate;
397
+ if (Array.isArray(config.ignoreErrors)) this.options.ignoreErrors = config.ignoreErrors;
398
+ if (typeof config.uploadInterval === "number") this.options.uploadInterval = config.uploadInterval;
399
+ if (typeof config.batchSize === "number") this.options.batchSize = config.batchSize;
400
+ if (typeof config.enablePerformance === "boolean") this.options.enablePerformance = config.enablePerformance;
401
+ if (typeof config.enableBehavior === "boolean") this.options.enableBehavior = config.enableBehavior;
402
+ } catch {
403
+ }
404
+ }
405
+ /** 上报用户资料;与日志队列隔离,避免资料重试或持久化到 IndexedDB。 */
406
+ async uploadUserProfile(userId, profile) {
407
+ const base = this.options.serverUrl.replace(/\/$/, "");
408
+ const res = await fetch(`${base}/api/users/profile?token=${encodeURIComponent(this.options.token)}`, {
409
+ method: "POST",
410
+ headers: { "Content-Type": "application/json" },
411
+ body: JSON.stringify({ projectId: this.options.projectId, userId, profile }),
412
+ signal: AbortSignal.timeout(5e3)
413
+ });
414
+ if (!res.ok) throw new Error(`[logger-sdk] user profile upload failed: HTTP ${res.status}`);
415
+ }
416
+ };
417
+
418
+ // src/plugins/error.ts
419
+ function installErrorPlugins(logger) {
420
+ const onError = (event) => {
421
+ const target = event.target;
422
+ if (target && target !== window && !event.message) {
423
+ const el = target;
424
+ const src = el.src || el.href;
425
+ if (src) {
426
+ logger.report({
427
+ type: "resource" /* Resource */,
428
+ level: "error",
429
+ message: `Resource load failed: ${el.tagName.toLowerCase()}`,
430
+ extra: { resourceUrl: src }
431
+ });
432
+ }
433
+ return;
434
+ }
435
+ logger.report({
436
+ type: "js-error" /* JsError */,
437
+ level: "error",
438
+ message: event.message,
439
+ stack: event.error?.stack,
440
+ extra: { filename: event.filename, lineno: event.lineno, colno: event.colno }
441
+ });
442
+ };
443
+ const onRejection = (event) => {
444
+ const reason = event.reason;
445
+ logger.report({
446
+ type: "promise-error" /* PromiseError */,
447
+ level: "error",
448
+ message: reason instanceof Error ? reason.message : String(reason),
449
+ stack: reason instanceof Error ? reason.stack : void 0
450
+ });
451
+ };
452
+ window.addEventListener("error", onError, true);
453
+ window.addEventListener("unhandledrejection", onRejection);
454
+ logger.addCleanup(() => {
455
+ window.removeEventListener("error", onError, true);
456
+ window.removeEventListener("unhandledrejection", onRejection);
457
+ });
458
+ }
459
+
460
+ // src/plugins/network.ts
461
+ var MAX_BODY_LENGTH = 10240;
462
+ function extractRequestBody(body) {
463
+ if (body == null) return void 0;
464
+ if (typeof body === "string") return body.length > MAX_BODY_LENGTH ? body.slice(0, MAX_BODY_LENGTH) : body;
465
+ if (body instanceof URLSearchParams) {
466
+ const s = body.toString();
467
+ return s.length > MAX_BODY_LENGTH ? s.slice(0, MAX_BODY_LENGTH) : s;
468
+ }
469
+ return void 0;
470
+ }
471
+ function installFetchPlugin(logger) {
472
+ const originalFetch = window.fetch.bind(window);
473
+ window.fetch = async (input, init2) => {
474
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
475
+ if (logger.shouldIgnoreUrl(url)) return originalFetch(input, init2);
476
+ const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
477
+ const requestBody = extractRequestBody(init2?.body);
478
+ const start = performance.now();
479
+ try {
480
+ const res = await originalFetch(input, init2);
481
+ const duration = Math.round(performance.now() - start);
482
+ logger.report({
483
+ type: "network" /* Network */,
484
+ level: res.ok ? "info" : "error",
485
+ message: `${method} ${url} ${res.status}`,
486
+ extra: { requestUrl: url, method, status: res.status, duration, success: res.ok, requestBody }
487
+ });
488
+ return res;
489
+ } catch (err) {
490
+ const duration = Math.round(performance.now() - start);
491
+ logger.report({
492
+ type: "network" /* Network */,
493
+ level: "error",
494
+ message: `${method} ${url} failed: ${err instanceof Error ? err.message : String(err)}`,
495
+ extra: { requestUrl: url, method, status: 0, duration, success: false, requestBody }
496
+ });
497
+ throw err;
498
+ }
499
+ };
500
+ logger.addCleanup(() => {
501
+ window.fetch = originalFetch;
502
+ });
503
+ }
504
+ function installXhrPlugin(logger) {
505
+ const OriginalXHR = window.XMLHttpRequest;
506
+ const originalOpen = OriginalXHR.prototype.open;
507
+ const originalSend = OriginalXHR.prototype.send;
508
+ OriginalXHR.prototype.open = function(method, url, ...rest) {
509
+ this.__logger = {
510
+ method: String(method).toUpperCase(),
511
+ url: typeof url === "string" ? url : url.href
512
+ };
513
+ return originalOpen.call(this, method, url, ...rest);
514
+ };
515
+ OriginalXHR.prototype.send = function(body) {
516
+ const meta = this.__logger;
517
+ if (!meta || logger.shouldIgnoreUrl(meta.url)) {
518
+ return originalSend.call(this, body);
519
+ }
520
+ meta.requestBody = extractRequestBody(body);
521
+ const start = performance.now();
522
+ const onLoadEnd = () => {
523
+ const duration = Math.round(performance.now() - start);
524
+ const status = this.status;
525
+ const success = status >= 200 && status < 300;
526
+ logger.report({
527
+ type: "network" /* Network */,
528
+ level: success ? "info" : "error",
529
+ message: `${meta.method} ${meta.url} ${status || "failed"}`,
530
+ extra: { requestUrl: meta.url, method: meta.method, status, duration, success, requestBody: meta.requestBody }
531
+ });
532
+ };
533
+ const onError = () => {
534
+ const duration = Math.round(performance.now() - start);
535
+ logger.report({
536
+ type: "network" /* Network */,
537
+ level: "error",
538
+ message: `${meta.method} ${meta.url} failed: network error`,
539
+ extra: { requestUrl: meta.url, method: meta.method, status: 0, duration, success: false, requestBody: meta.requestBody }
540
+ });
541
+ };
542
+ this.addEventListener("loadend", onLoadEnd);
543
+ this.addEventListener("error", onError);
544
+ return originalSend.call(this, body);
545
+ };
546
+ logger.addCleanup(() => {
547
+ OriginalXHR.prototype.open = originalOpen;
548
+ OriginalXHR.prototype.send = originalSend;
549
+ });
550
+ }
551
+ function attachAxiosTo(logger, axios) {
552
+ axios.interceptors.request.use((config) => {
553
+ config.metadata = { start: performance.now(), requestBody: config.data != null ? extractRequestBody(config.data) : void 0 };
554
+ return config;
555
+ });
556
+ axios.interceptors.response.use(
557
+ (res) => {
558
+ const url = res.config.url ?? "";
559
+ if (!logger.shouldIgnoreUrl(url)) {
560
+ const duration = Math.round(performance.now() - (res.config.metadata?.start ?? performance.now()));
561
+ logger.report({
562
+ type: "network" /* Network */,
563
+ level: "info",
564
+ message: `${res.config.method?.toUpperCase()} ${url} ${res.status}`,
565
+ extra: { requestUrl: url, method: res.config.method, status: res.status, duration, success: true, requestBody: res.config.metadata?.requestBody }
566
+ });
567
+ }
568
+ return res;
569
+ },
570
+ (err) => {
571
+ const url = err.config?.url ?? "";
572
+ if (!logger.shouldIgnoreUrl(url)) {
573
+ const duration = Math.round(performance.now() - (err.config?.metadata?.start ?? performance.now()));
574
+ logger.report({
575
+ type: "network" /* Network */,
576
+ level: "error",
577
+ message: `${err.config?.method?.toUpperCase()} ${url} failed: ${err.message}`,
578
+ extra: { requestUrl: url, method: err.config?.method, status: err.response?.status ?? 0, duration, success: false, requestBody: err.config?.metadata?.requestBody }
579
+ });
580
+ }
581
+ return Promise.reject(err);
582
+ }
583
+ );
584
+ }
585
+
586
+ // src/plugins/performance.ts
587
+ function installPerformancePlugin(logger) {
588
+ if (!logger.options.enablePerformance || typeof PerformanceObserver === "undefined") return;
589
+ const metrics = {};
590
+ const observers = [];
591
+ const observe = (type, cb) => {
592
+ try {
593
+ const ob = new PerformanceObserver((list) => cb(list.getEntries()));
594
+ ob.observe({ type, buffered: true });
595
+ observers.push(ob);
596
+ } catch {
597
+ }
598
+ };
599
+ observe("largest-contentful-paint", (entries) => {
600
+ const last = entries[entries.length - 1];
601
+ if (last) metrics.lcp = Math.round(last.startTime);
602
+ });
603
+ observe("paint", (entries) => {
604
+ for (const e of entries) {
605
+ if (e.name === "first-contentful-paint") metrics.fcp = Math.round(e.startTime);
606
+ }
607
+ });
608
+ observe("first-input", (entries) => {
609
+ const first = entries[0];
610
+ if (first) metrics.fid = Math.round(first.processingStart - first.startTime);
611
+ });
612
+ const reportOnce = /* @__PURE__ */ (() => {
613
+ let reported = false;
614
+ return () => {
615
+ if (reported) return;
616
+ reported = true;
617
+ const nav = performance.getEntriesByType("navigation")[0];
618
+ if (nav) metrics.ttfb = Math.round(nav.responseStart - nav.requestStart);
619
+ logger.report({
620
+ type: "performance" /* Performance */,
621
+ level: "info",
622
+ message: "page-performance",
623
+ extra: { ...metrics }
624
+ });
625
+ };
626
+ })();
627
+ const onHidden = () => {
628
+ if (document.visibilityState === "hidden") reportOnce();
629
+ };
630
+ document.addEventListener("visibilitychange", onHidden);
631
+ const timer = setTimeout(reportOnce, 1e4);
632
+ logger.addCleanup(() => {
633
+ observers.forEach((o) => o.disconnect());
634
+ document.removeEventListener("visibilitychange", onHidden);
635
+ clearTimeout(timer);
636
+ });
637
+ }
638
+
639
+ // src/plugins/behavior.ts
640
+ var MAX_PATH_DEPTH = 6;
641
+ function buildDomPath(el) {
642
+ const parts = [];
643
+ let node = el;
644
+ while (node && node !== document.body && parts.length < MAX_PATH_DEPTH) {
645
+ const tag = node.tagName.toLowerCase();
646
+ const id = node.id ? `#${node.id}` : "";
647
+ const cls = node.className && typeof node.className === "string" ? `.${node.className.split(/\s+/).slice(0, 2).join(".")}` : "";
648
+ parts.unshift(`${tag}${id}${cls}`);
649
+ node = node.parentElement;
650
+ }
651
+ parts.unshift("body");
652
+ return parts.join(" > ");
653
+ }
654
+ function installBehaviorPlugin(logger) {
655
+ if (!logger.options.enableBehavior) return;
656
+ const onClick = (event) => {
657
+ const target = event.target;
658
+ if (!target) return;
659
+ const text = (target.innerText ?? "").trim().slice(0, 50);
660
+ const selector = [
661
+ target.tagName.toLowerCase(),
662
+ target.id ? `#${target.id}` : "",
663
+ target.className && typeof target.className === "string" ? `.${target.className.split(/\s+/).slice(0, 2).join(".")}` : ""
664
+ ].join("");
665
+ logger.report({
666
+ type: "behavior" /* Behavior */,
667
+ level: "info",
668
+ message: `click ${selector}`,
669
+ extra: { selector, path: buildDomPath(target), text }
670
+ });
671
+ };
672
+ document.addEventListener("click", onClick, true);
673
+ logger.addCleanup(() => document.removeEventListener("click", onClick, true));
674
+ }
675
+ function installPagePlugin(logger) {
676
+ const reportPv = (from) => {
677
+ logger.report({
678
+ type: "page" /* Page */,
679
+ level: "info",
680
+ message: "pv",
681
+ extra: { from, referrer: document.referrer }
682
+ });
683
+ };
684
+ reportPv();
685
+ const originalPushState = history.pushState.bind(history);
686
+ const originalReplaceState = history.replaceState.bind(history);
687
+ let lastUrl = location.href;
688
+ const onRouteChange = () => {
689
+ if (location.href === lastUrl) return;
690
+ const from = lastUrl;
691
+ lastUrl = location.href;
692
+ reportPv(from);
693
+ };
694
+ history.pushState = (...args) => {
695
+ originalPushState(...args);
696
+ onRouteChange();
697
+ };
698
+ history.replaceState = (...args) => {
699
+ originalReplaceState(...args);
700
+ onRouteChange();
701
+ };
702
+ window.addEventListener("popstate", onRouteChange);
703
+ window.addEventListener("hashchange", onRouteChange);
704
+ logger.addCleanup(() => {
705
+ history.pushState = originalPushState;
706
+ history.replaceState = originalReplaceState;
707
+ window.removeEventListener("popstate", onRouteChange);
708
+ window.removeEventListener("hashchange", onRouteChange);
709
+ });
710
+ }
711
+ function installConsolePlugin(logger) {
712
+ if (!logger.options.enableConsole) return;
713
+ const levels = ["error", "warn"];
714
+ const originals = /* @__PURE__ */ new Map();
715
+ for (const level of levels) {
716
+ const original = console[level].bind(console);
717
+ originals.set(level, original);
718
+ console[level] = (...args) => {
719
+ original(...args);
720
+ logger.report({
721
+ type: "console" /* Console */,
722
+ level,
723
+ message: args.map((a) => typeof a === "object" ? safeStringify(a) : String(a)).join(" ").slice(0, 2e3)
724
+ });
725
+ };
726
+ }
727
+ logger.addCleanup(() => {
728
+ for (const level of levels) {
729
+ const original = originals.get(level);
730
+ if (original) console[level] = original;
731
+ }
732
+ });
733
+ }
734
+ function safeStringify(value) {
735
+ try {
736
+ return JSON.stringify(value);
737
+ } catch {
738
+ return String(value);
739
+ }
740
+ }
741
+
742
+ // src/plugins/vue.ts
743
+ function createVuePlugin(logger) {
744
+ return {
745
+ install(app) {
746
+ const previous = app.config.errorHandler;
747
+ app.config.errorHandler = (err, instance2, info) => {
748
+ const comp = instance2;
749
+ const componentName = comp?.$options?.name ?? comp?.$options?.__name ?? comp?.$?.type?.name ?? comp?.$?.type?.__name ?? "anonymous";
750
+ const error = err instanceof Error ? err : new Error(String(err));
751
+ logger.report({
752
+ type: "vue-error" /* VueError */,
753
+ level: "error",
754
+ message: error.message,
755
+ stack: error.stack,
756
+ extra: { componentName, lifecycleHook: info }
757
+ });
758
+ if (previous) previous(err, instance2, info);
759
+ };
760
+ }
761
+ };
762
+ }
763
+
764
+ // src/index.ts
765
+ var instance = null;
766
+ function init(options) {
767
+ if (instance) return instance;
768
+ instance = new Logger(options);
769
+ if (instance.options.enableError) installErrorPlugins(instance);
770
+ if (instance.options.enableNetwork) {
771
+ installFetchPlugin(instance);
772
+ installXhrPlugin(instance);
773
+ }
774
+ if (instance.options.enablePerformance) installPerformancePlugin(instance);
775
+ if (instance.options.enableBehavior) installBehaviorPlugin(instance);
776
+ if (instance.options.enableConsole) installConsolePlugin(instance);
777
+ installPagePlugin(instance);
778
+ return instance;
779
+ }
780
+ function required() {
781
+ if (!instance) throw new Error("[logger-sdk] init() must be called first");
782
+ return instance;
783
+ }
784
+ function setUser(userId, profile) {
785
+ required().setUser(userId, profile);
786
+ }
787
+ function clearUser() {
788
+ required().clearUser();
789
+ }
790
+ function setTag(key, value) {
791
+ required().setTag(key, value);
792
+ }
793
+ function track(event, data) {
794
+ required().track(event, data);
795
+ }
796
+ function captureError(error, extra) {
797
+ required().captureError(error, extra);
798
+ }
799
+ function captureMessage(message, level) {
800
+ required().captureMessage(message, level);
801
+ }
802
+ function getSessionId() {
803
+ return required().sessionId;
804
+ }
805
+ function getTraceId() {
806
+ return required().traceId;
807
+ }
808
+ function flush() {
809
+ return required().flush();
810
+ }
811
+ function destroy() {
812
+ instance?.destroy();
813
+ instance = null;
814
+ }
815
+ function attachAxios(axios) {
816
+ attachAxiosTo(required(), axios);
817
+ }
818
+ var VuePlugin = {
819
+ install(app) {
820
+ createVuePlugin(required()).install(app);
821
+ }
822
+ };
823
+ // Annotate the CommonJS export names for ESM import in node:
824
+ 0 && (module.exports = {
825
+ LogType,
826
+ Logger,
827
+ VuePlugin,
828
+ attachAxios,
829
+ captureError,
830
+ captureMessage,
831
+ clearUser,
832
+ destroy,
833
+ flush,
834
+ getSessionId,
835
+ getTraceId,
836
+ init,
837
+ scrubUrl,
838
+ setTag,
839
+ setUser,
840
+ track
841
+ });