@moonwatch/js 0.1.8

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.mjs ADDED
@@ -0,0 +1,860 @@
1
+ import {
2
+ __privateAdd,
3
+ __privateMethod
4
+ } from "./chunk-JKIIIVNW.mjs";
5
+
6
+ // ../node_modules/non-error/index.js
7
+ var isNonErrorSymbol = /* @__PURE__ */ Symbol("isNonError");
8
+ function defineProperty(object, key, value) {
9
+ Object.defineProperty(object, key, {
10
+ value,
11
+ writable: false,
12
+ enumerable: false,
13
+ configurable: false
14
+ });
15
+ }
16
+ function stringify(value) {
17
+ if (value === void 0) {
18
+ return "undefined";
19
+ }
20
+ if (value === null) {
21
+ return "null";
22
+ }
23
+ if (typeof value === "string") {
24
+ return value;
25
+ }
26
+ if (typeof value === "number" || typeof value === "boolean") {
27
+ return String(value);
28
+ }
29
+ if (typeof value === "bigint") {
30
+ return `${value}n`;
31
+ }
32
+ if (typeof value === "symbol") {
33
+ return value.toString();
34
+ }
35
+ if (typeof value === "function") {
36
+ return `[Function${value.name ? ` ${value.name}` : " (anonymous)"}]`;
37
+ }
38
+ if (value instanceof Error) {
39
+ try {
40
+ return String(value);
41
+ } catch {
42
+ return "<Unserializable error>";
43
+ }
44
+ }
45
+ try {
46
+ return JSON.stringify(value);
47
+ } catch {
48
+ try {
49
+ return String(value);
50
+ } catch {
51
+ return "<Unserializable value>";
52
+ }
53
+ }
54
+ }
55
+ var _NonError_static, handleCallback_fn;
56
+ var _NonError = class _NonError extends Error {
57
+ constructor(value, { superclass: Superclass = Error } = {}) {
58
+ if (_NonError.isNonError(value)) {
59
+ return value;
60
+ }
61
+ if (value instanceof Error) {
62
+ throw new TypeError("Do not pass Error instances to NonError. Throw the error directly instead.");
63
+ }
64
+ super(`Non-error value: ${stringify(value)}`);
65
+ if (Superclass !== Error) {
66
+ Object.setPrototypeOf(this, Superclass.prototype);
67
+ }
68
+ defineProperty(this, "name", "NonError");
69
+ defineProperty(this, isNonErrorSymbol, true);
70
+ defineProperty(this, "isNonError", true);
71
+ defineProperty(this, "value", value);
72
+ }
73
+ static isNonError(value) {
74
+ return value?.[isNonErrorSymbol] === true;
75
+ }
76
+ static try(callback) {
77
+ var _a;
78
+ return __privateMethod(_a = _NonError, _NonError_static, handleCallback_fn).call(_a, callback, []);
79
+ }
80
+ static wrap(callback) {
81
+ return (...arguments_) => {
82
+ var _a;
83
+ return __privateMethod(_a = _NonError, _NonError_static, handleCallback_fn).call(_a, callback, arguments_);
84
+ };
85
+ }
86
+ // This makes instanceof work even when using the `superclass` option
87
+ static [Symbol.hasInstance](instance) {
88
+ return _NonError.isNonError(instance);
89
+ }
90
+ };
91
+ _NonError_static = new WeakSet();
92
+ handleCallback_fn = function(callback, arguments_) {
93
+ try {
94
+ const result = callback(...arguments_);
95
+ if (result && typeof result.then === "function") {
96
+ return (async () => {
97
+ try {
98
+ return await result;
99
+ } catch (error) {
100
+ if (error instanceof Error) {
101
+ throw error;
102
+ }
103
+ throw new _NonError(error);
104
+ }
105
+ })();
106
+ }
107
+ return result;
108
+ } catch (error) {
109
+ if (error instanceof Error) {
110
+ throw error;
111
+ }
112
+ throw new _NonError(error);
113
+ }
114
+ };
115
+ __privateAdd(_NonError, _NonError_static);
116
+ var NonError = _NonError;
117
+
118
+ // ../node_modules/serialize-error/error-constructors.js
119
+ var list = [
120
+ // Native ES errors https://262.ecma-international.org/12.0/#sec-well-known-intrinsic-objects
121
+ Error,
122
+ EvalError,
123
+ RangeError,
124
+ ReferenceError,
125
+ SyntaxError,
126
+ TypeError,
127
+ URIError,
128
+ AggregateError,
129
+ // Built-in errors
130
+ globalThis.DOMException,
131
+ // Node-specific errors
132
+ // https://nodejs.org/api/errors.html
133
+ globalThis.AssertionError,
134
+ globalThis.SystemError
135
+ ].filter(Boolean).map((constructor) => [constructor.name, constructor]);
136
+ var errorConstructors = new Map(list);
137
+ var errorFactories = /* @__PURE__ */ new Map();
138
+
139
+ // ../node_modules/serialize-error/index.js
140
+ var errorProperties = [
141
+ {
142
+ property: "name",
143
+ enumerable: false
144
+ },
145
+ {
146
+ property: "message",
147
+ enumerable: false
148
+ },
149
+ {
150
+ property: "stack",
151
+ enumerable: false
152
+ },
153
+ {
154
+ property: "code",
155
+ enumerable: true
156
+ },
157
+ {
158
+ property: "cause",
159
+ enumerable: false
160
+ },
161
+ {
162
+ property: "errors",
163
+ enumerable: false
164
+ }
165
+ ];
166
+ var toJsonWasCalled = /* @__PURE__ */ new WeakSet();
167
+ var toJSON = (from) => {
168
+ toJsonWasCalled.add(from);
169
+ const json = from.toJSON();
170
+ toJsonWasCalled.delete(from);
171
+ return json;
172
+ };
173
+ var newError = (name) => {
174
+ if (name === "NonError") {
175
+ return new NonError();
176
+ }
177
+ const factory = errorFactories.get(name);
178
+ if (factory) {
179
+ return factory();
180
+ }
181
+ const ErrorConstructor = errorConstructors.get(name) ?? Error;
182
+ return ErrorConstructor === AggregateError ? new ErrorConstructor([]) : new ErrorConstructor();
183
+ };
184
+ var destroyCircular = ({
185
+ from,
186
+ seen,
187
+ to,
188
+ forceEnumerable,
189
+ maxDepth,
190
+ depth,
191
+ useToJSON,
192
+ serialize
193
+ }) => {
194
+ if (!to) {
195
+ if (Array.isArray(from)) {
196
+ to = [];
197
+ } else if (!serialize && isErrorLike(from)) {
198
+ to = newError(from.name);
199
+ } else {
200
+ to = {};
201
+ }
202
+ }
203
+ seen.add(from);
204
+ if (depth >= maxDepth) {
205
+ seen.delete(from);
206
+ return to;
207
+ }
208
+ if (useToJSON && typeof from.toJSON === "function" && !toJsonWasCalled.has(from)) {
209
+ seen.delete(from);
210
+ return toJSON(from);
211
+ }
212
+ const continueDestroyCircular = (value) => destroyCircular({
213
+ from: value,
214
+ seen,
215
+ forceEnumerable,
216
+ maxDepth,
217
+ depth: depth + 1,
218
+ useToJSON,
219
+ serialize
220
+ });
221
+ for (const key of Object.keys(from)) {
222
+ const value = from[key];
223
+ if (value && value instanceof Uint8Array && value.constructor.name === "Buffer") {
224
+ to[key] = serialize ? "[object Buffer]" : value;
225
+ continue;
226
+ }
227
+ if (value !== null && typeof value === "object" && typeof value.pipe === "function") {
228
+ to[key] = serialize ? "[object Stream]" : value;
229
+ continue;
230
+ }
231
+ if (typeof value === "function") {
232
+ if (!serialize) {
233
+ to[key] = value;
234
+ }
235
+ continue;
236
+ }
237
+ if (serialize && typeof value === "bigint") {
238
+ to[key] = `${value}n`;
239
+ continue;
240
+ }
241
+ if (!value || typeof value !== "object") {
242
+ try {
243
+ to[key] = value;
244
+ } catch {
245
+ }
246
+ continue;
247
+ }
248
+ if (!seen.has(value)) {
249
+ to[key] = continueDestroyCircular(value);
250
+ continue;
251
+ }
252
+ to[key] = "[Circular]";
253
+ }
254
+ if (serialize || to instanceof Error) {
255
+ for (const { property, enumerable } of errorProperties) {
256
+ const value = from[property];
257
+ if (value === void 0 || value === null) {
258
+ continue;
259
+ }
260
+ const descriptor = Object.getOwnPropertyDescriptor(to, property);
261
+ if (descriptor?.configurable === false) {
262
+ continue;
263
+ }
264
+ let processedValue = value;
265
+ if (typeof value === "object") {
266
+ processedValue = seen.has(value) ? "[Circular]" : continueDestroyCircular(value);
267
+ }
268
+ Object.defineProperty(to, property, {
269
+ value: processedValue,
270
+ enumerable: forceEnumerable || enumerable,
271
+ configurable: true,
272
+ writable: true
273
+ });
274
+ }
275
+ }
276
+ seen.delete(from);
277
+ return to;
278
+ };
279
+ function serializeError(value, options = {}) {
280
+ const {
281
+ maxDepth = Number.POSITIVE_INFINITY,
282
+ useToJSON = true
283
+ } = options;
284
+ if (typeof value === "object" && value !== null) {
285
+ return destroyCircular({
286
+ from: value,
287
+ seen: /* @__PURE__ */ new Set(),
288
+ forceEnumerable: true,
289
+ maxDepth,
290
+ depth: 0,
291
+ useToJSON,
292
+ serialize: true
293
+ });
294
+ }
295
+ if (typeof value === "function") {
296
+ value = "<Function>";
297
+ }
298
+ return destroyCircular({
299
+ from: new NonError(value),
300
+ seen: /* @__PURE__ */ new Set(),
301
+ forceEnumerable: true,
302
+ maxDepth,
303
+ depth: 0,
304
+ useToJSON,
305
+ serialize: true
306
+ });
307
+ }
308
+ function isErrorLike(value) {
309
+ return Boolean(value) && typeof value === "object" && typeof value.name === "string" && typeof value.message === "string" && typeof value.stack === "string";
310
+ }
311
+
312
+ // src/index.ts
313
+ var RELEASE_ID = typeof __MOONWATCH_RELEASE__ !== "undefined" ? __MOONWATCH_RELEASE__ : void 0;
314
+ var LEVEL_ORDER = {
315
+ DEBUG: 0,
316
+ INFO: 1,
317
+ WARN: 2,
318
+ ERROR: 3,
319
+ FATAL: 4
320
+ };
321
+ var SDK_VERSION = "0.1.4";
322
+ var DEFAULT_BASE_URL = "https://log.terodato.com";
323
+ var BATCH_SIZE = 50;
324
+ var MAX_BUFFER_SIZE = 1e3;
325
+ var FLUSH_INTERVAL_MS = 1e3;
326
+ var FETCH_TIMEOUT_MS = 1e4;
327
+ var batchCounter = 0;
328
+ var generateBatchId = () => `${Date.now()}-${++batchCounter}`;
329
+ var originalConsole = {
330
+ log: console.log.bind(console),
331
+ info: console.info.bind(console),
332
+ warn: console.warn.bind(console),
333
+ error: console.error.bind(console),
334
+ debug: console.debug.bind(console)
335
+ };
336
+ function resolveLevel(level) {
337
+ if (!level) return "DEBUG";
338
+ if (level in LEVEL_ORDER) return level;
339
+ return "INFO";
340
+ }
341
+ function safeStringify(value) {
342
+ try {
343
+ return JSON.stringify(value);
344
+ } catch {
345
+ const seen = /* @__PURE__ */ new WeakSet();
346
+ return JSON.stringify(value, (_key, val) => {
347
+ if (typeof val === "object" && val !== null) {
348
+ if (seen.has(val)) return "[Circular]";
349
+ seen.add(val);
350
+ }
351
+ return val;
352
+ });
353
+ }
354
+ }
355
+ var _Logger = class _Logger {
356
+ constructor(config) {
357
+ this.buffer = [];
358
+ this.flushTimer = null;
359
+ this.flushPromise = null;
360
+ this.consoleIntercepted = false;
361
+ this.httpBackoffMs = 0;
362
+ this.maxBackoffMs = 6e4;
363
+ this.consecutiveHttpFailures = 0;
364
+ // Error capture via window event listeners (no console.error wrapping to
365
+ // avoid polluting stack traces shown in Next.js / React error overlays)
366
+ this._onError = null;
367
+ this._onUnhandledRejection = null;
368
+ // WebSocket state
369
+ this.ws = null;
370
+ this.wsState = "disconnected";
371
+ this.wsReconnectTimer = null;
372
+ this.wsReconnectAttempts = 0;
373
+ this.maxWsReconnectAttempts = 5;
374
+ // Browser lifecycle hooks for best-effort flush on page unload
375
+ this._onVisibilityChange = null;
376
+ this._onBeforeUnload = null;
377
+ this.config = {
378
+ logId: config.logId,
379
+ apiKey: config.apiKey,
380
+ group: config.group,
381
+ level: resolveLevel(config.level),
382
+ onError: config.onError,
383
+ traceId: config.traceId,
384
+ silent: config.silent ?? false,
385
+ debug: config.debug ?? false
386
+ };
387
+ this.proxyMode = !!config._ingestUrl;
388
+ const baseUrl = config._endpoint ?? DEFAULT_BASE_URL;
389
+ this.baseUrl = baseUrl;
390
+ const wsBaseUrl = config._wsEndpoint ?? baseUrl;
391
+ this.httpIngestUrl = config._ingestUrl ?? `${baseUrl}/ingest/http`;
392
+ this.wsIngestUrl = `${wsBaseUrl.replace(/^http/, "ws")}/ingest/ws`;
393
+ this.startFlushTimer();
394
+ if (!this.proxyMode && typeof WebSocket !== "undefined") {
395
+ this.connectWebSocket();
396
+ } else if (this.proxyMode) {
397
+ this.debugLog("Proxy mode enabled, using HTTP transport only");
398
+ } else {
399
+ this.debugLog("WebSocket not available, using HTTP transport only");
400
+ }
401
+ this.registerLifecycleHooks();
402
+ }
403
+ debugLog(msg, ...args) {
404
+ if (this.config.debug) {
405
+ originalConsole.debug("[Moonwatch]", msg, ...args);
406
+ }
407
+ }
408
+ connectWebSocket() {
409
+ if (this.wsState === "connecting" || this.wsState === "connected") {
410
+ return;
411
+ }
412
+ this.wsState = "connecting";
413
+ this.debugLog("WebSocket connecting to", this.wsIngestUrl);
414
+ try {
415
+ this.ws = new WebSocket(this.wsIngestUrl, ["api_key", this.config.apiKey]);
416
+ this.ws.onopen = () => {
417
+ this.wsReconnectAttempts = 0;
418
+ this.wsState = "connected";
419
+ this.debugLog("WebSocket connected");
420
+ };
421
+ this.ws.onmessage = (event) => {
422
+ try {
423
+ const msg = JSON.parse(event.data);
424
+ if (!msg.success) {
425
+ originalConsole.error("[Moonwatch] WebSocket log ingestion failed:", msg.error);
426
+ }
427
+ } catch {
428
+ }
429
+ };
430
+ this.ws.onerror = () => {
431
+ };
432
+ this.ws.onclose = () => {
433
+ this.wsState = "disconnected";
434
+ this.ws = null;
435
+ if (this.wsReconnectAttempts < this.maxWsReconnectAttempts) {
436
+ const delay = Math.min(1e3 * Math.pow(2, this.wsReconnectAttempts), 3e4);
437
+ this.wsReconnectAttempts++;
438
+ this.debugLog(`WebSocket disconnected, reconnecting in ${delay}ms (attempt ${this.wsReconnectAttempts}/${this.maxWsReconnectAttempts})`);
439
+ this.wsReconnectTimer = setTimeout(() => this.connectWebSocket(), delay);
440
+ } else {
441
+ this.debugLog("WebSocket max reconnect attempts reached, using HTTP only");
442
+ }
443
+ };
444
+ } catch {
445
+ this.wsState = "failed";
446
+ this.debugLog("WebSocket connection failed");
447
+ }
448
+ }
449
+ startFlushTimer() {
450
+ if (this.flushTimer) {
451
+ clearInterval(this.flushTimer);
452
+ }
453
+ this.flushTimer = setInterval(() => {
454
+ this.flush();
455
+ }, FLUSH_INTERVAL_MS);
456
+ }
457
+ shouldLog(level) {
458
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[this.config.level];
459
+ }
460
+ formatArgs(args) {
461
+ return args.map((arg) => {
462
+ if (typeof arg === "string") return arg;
463
+ if (arg instanceof Error) return `${arg.message}
464
+ ${arg.stack}`;
465
+ try {
466
+ return JSON.stringify(arg, null, 2);
467
+ } catch {
468
+ return String(arg);
469
+ }
470
+ }).join(" ");
471
+ }
472
+ log(level, message, options) {
473
+ if (!this.shouldLog(level)) return;
474
+ if (!this.config.silent) {
475
+ const method = _Logger.CONSOLE_METHOD[level];
476
+ const consoleArgs = [message];
477
+ if (options?.metadata && Object.keys(options.metadata).length > 0) {
478
+ consoleArgs.push(options.metadata);
479
+ }
480
+ if (options?.stack) {
481
+ consoleArgs.push("\n" + options.stack);
482
+ }
483
+ originalConsole[method](...consoleArgs);
484
+ }
485
+ const entry = {
486
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
487
+ level,
488
+ message,
489
+ logId: this.config.logId,
490
+ group: options?.group ?? this.config.group,
491
+ trace_id: options?.traceId ?? this.config.traceId,
492
+ stack: options?.stack,
493
+ error_type: options?.errorType,
494
+ release_id: RELEASE_ID,
495
+ watcher_id: options?.watcherId,
496
+ metadata: options?.metadata
497
+ };
498
+ this.buffer.push(entry);
499
+ if (this.buffer.length > MAX_BUFFER_SIZE) {
500
+ const dropped = this.buffer.length - MAX_BUFFER_SIZE;
501
+ this.buffer.splice(0, dropped);
502
+ this.debugLog(`Buffer full, dropped ${dropped} oldest logs`);
503
+ }
504
+ if (this.buffer.length >= BATCH_SIZE) {
505
+ this.flush();
506
+ }
507
+ }
508
+ parseArgs(msgOrObj, metadata) {
509
+ if (typeof msgOrObj === "object") {
510
+ return {
511
+ message: msgOrObj.message,
512
+ options: { traceId: msgOrObj.traceId, group: msgOrObj.group, watcherId: msgOrObj.watcherId, metadata: msgOrObj.metadata }
513
+ };
514
+ }
515
+ if (metadata !== void 0) {
516
+ return { message: msgOrObj, options: { metadata } };
517
+ }
518
+ return { message: msgOrObj };
519
+ }
520
+ debug(msgOrObj, metadata) {
521
+ const { message, options } = this.parseArgs(msgOrObj, metadata);
522
+ this.log("DEBUG", message, options);
523
+ }
524
+ info(msgOrObj, metadata) {
525
+ const { message, options } = this.parseArgs(msgOrObj, metadata);
526
+ this.log("INFO", message, options);
527
+ }
528
+ warn(msgOrObj, metadata) {
529
+ const { message, options } = this.parseArgs(msgOrObj, metadata);
530
+ this.log("WARN", message, options);
531
+ }
532
+ error(msgOrObj, metadata) {
533
+ if (msgOrObj instanceof Error) {
534
+ this.log("ERROR", msgOrObj.message, {
535
+ metadata,
536
+ stack: msgOrObj.stack,
537
+ errorType: msgOrObj.name
538
+ });
539
+ return;
540
+ }
541
+ const { message, options } = this.parseArgs(msgOrObj, metadata);
542
+ this.log("ERROR", message, options);
543
+ }
544
+ fatal(msgOrObj, metadata) {
545
+ if (msgOrObj instanceof Error) {
546
+ this.log("FATAL", msgOrObj.message, {
547
+ metadata,
548
+ stack: msgOrObj.stack,
549
+ errorType: msgOrObj.name
550
+ });
551
+ return;
552
+ }
553
+ const { message, options } = this.parseArgs(msgOrObj, metadata);
554
+ this.log("FATAL", message, options);
555
+ }
556
+ withTraceId(traceId) {
557
+ return new ScopedLogger(this, void 0, traceId, void 0);
558
+ }
559
+ withGroup(group) {
560
+ return new ScopedLogger(this, group, void 0, void 0);
561
+ }
562
+ withWatcher(watcherId) {
563
+ return new ScopedLogger(this, void 0, void 0, watcherId);
564
+ }
565
+ setTraceId(traceId) {
566
+ this.config.traceId = traceId;
567
+ }
568
+ setGroup(group) {
569
+ this.config.group = group;
570
+ }
571
+ interceptConsole(group, options) {
572
+ if (this.consoleIntercepted) return;
573
+ this.consoleIntercepted = true;
574
+ const self = this;
575
+ const logGroup = group ?? "console";
576
+ const wrap = (original, level) => {
577
+ const wrapped = (...args) => {
578
+ let stack;
579
+ let errorType;
580
+ if (level === "ERROR") {
581
+ const errArg = args.find((a) => isErrorLike(a));
582
+ if (errArg) {
583
+ const serialized = serializeError(errArg);
584
+ stack = serialized.stack;
585
+ errorType = serialized.name;
586
+ } else {
587
+ const lines = new Error().stack?.split("\n");
588
+ if (lines) {
589
+ const isV8 = lines[0]?.trimStart().startsWith("Error");
590
+ stack = lines.slice(isV8 ? 2 : 1).join("\n");
591
+ }
592
+ }
593
+ }
594
+ queueMicrotask(() => {
595
+ self.log(level, self.formatArgs(args), { group: logGroup, stack, errorType });
596
+ });
597
+ return original.apply(console, args);
598
+ };
599
+ Object.defineProperty(wrapped, "name", { value: original.name });
600
+ return wrapped;
601
+ };
602
+ console.debug = wrap(originalConsole.debug, "DEBUG");
603
+ console.log = wrap(originalConsole.log, "INFO");
604
+ console.info = wrap(originalConsole.info, "INFO");
605
+ console.warn = wrap(originalConsole.warn, "WARN");
606
+ if (options?.wrapErrors !== false) {
607
+ console.error = wrap(originalConsole.error, "ERROR");
608
+ }
609
+ if (typeof window !== "undefined") {
610
+ this._onError = (event) => {
611
+ self.log("ERROR", event.message, {
612
+ group: logGroup,
613
+ stack: event.error?.stack,
614
+ errorType: event.error?.name
615
+ });
616
+ };
617
+ this._onUnhandledRejection = (event) => {
618
+ const err = event.reason;
619
+ const message = err instanceof Error ? err.message : String(err);
620
+ self.log("ERROR", message, {
621
+ group: logGroup,
622
+ stack: err instanceof Error ? err.stack : void 0,
623
+ errorType: err instanceof Error ? err.name : "UnhandledRejection"
624
+ });
625
+ };
626
+ window.addEventListener("error", this._onError);
627
+ window.addEventListener("unhandledrejection", this._onUnhandledRejection);
628
+ }
629
+ }
630
+ restoreConsole() {
631
+ if (!this.consoleIntercepted) return;
632
+ this.consoleIntercepted = false;
633
+ console.debug = originalConsole.debug;
634
+ console.log = originalConsole.log;
635
+ console.info = originalConsole.info;
636
+ console.warn = originalConsole.warn;
637
+ if (console.error !== originalConsole.error) {
638
+ console.error = originalConsole.error;
639
+ }
640
+ if (typeof window !== "undefined") {
641
+ if (this._onError) {
642
+ window.removeEventListener("error", this._onError);
643
+ this._onError = null;
644
+ }
645
+ if (this._onUnhandledRejection) {
646
+ window.removeEventListener("unhandledrejection", this._onUnhandledRejection);
647
+ this._onUnhandledRejection = null;
648
+ }
649
+ }
650
+ }
651
+ async flush() {
652
+ if (this.flushPromise) {
653
+ await this.flushPromise;
654
+ }
655
+ if (this.buffer.length === 0) return;
656
+ const logs = this.buffer.splice(0, this.buffer.length);
657
+ this.flushPromise = this.doFlush(logs);
658
+ try {
659
+ await this.flushPromise;
660
+ } finally {
661
+ this.flushPromise = null;
662
+ }
663
+ }
664
+ async doFlush(logs) {
665
+ const batchId = generateBatchId();
666
+ if (this.wsState === "connected" && typeof WebSocket !== "undefined") {
667
+ const sent = this.sendViaWebSocket(logs, batchId);
668
+ if (sent) return;
669
+ }
670
+ await this.sendViaHttp(logs, batchId);
671
+ }
672
+ sendViaWebSocket(logs, batchId) {
673
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
674
+ return false;
675
+ }
676
+ try {
677
+ this.ws.send(safeStringify({ type: "logs", batchId, logs }));
678
+ this.debugLog(`Flushed ${logs.length} logs via WebSocket (batch ${batchId})`);
679
+ return true;
680
+ } catch {
681
+ this.debugLog("WebSocket send failed, falling back to HTTP");
682
+ return false;
683
+ }
684
+ }
685
+ async sendViaHttp(logs, batchId) {
686
+ if (this.httpBackoffMs > 0) {
687
+ await new Promise((resolve) => setTimeout(resolve, this.httpBackoffMs));
688
+ }
689
+ const controller = new AbortController();
690
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
691
+ try {
692
+ const headers = {
693
+ "Content-Type": "application/json",
694
+ "X-Batch-Id": batchId,
695
+ "X-SDK-Version": SDK_VERSION
696
+ };
697
+ if (this.config.apiKey) {
698
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
699
+ }
700
+ const response = await fetch(this.httpIngestUrl, {
701
+ method: "POST",
702
+ headers,
703
+ body: safeStringify(logs),
704
+ signal: controller.signal
705
+ });
706
+ if (!response.ok) {
707
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
708
+ throw new Error(error.error || `HTTP ${response.status}`);
709
+ }
710
+ this.httpBackoffMs = 0;
711
+ this.consecutiveHttpFailures = 0;
712
+ this.debugLog(`Flushed ${logs.length} logs via HTTP (batch ${batchId})`);
713
+ } catch (err) {
714
+ this.buffer.unshift(...logs);
715
+ this.consecutiveHttpFailures++;
716
+ this.httpBackoffMs = Math.min(
717
+ Math.pow(2, this.consecutiveHttpFailures - 1) * 1e3,
718
+ this.maxBackoffMs
719
+ );
720
+ this.debugLog(`HTTP flush failed (retry in ${this.httpBackoffMs / 1e3}s):`, err);
721
+ if (this.config.onError) {
722
+ this.config.onError(err, logs);
723
+ } else {
724
+ originalConsole.error(
725
+ `[Moonwatch] Failed to flush logs (retry in ${this.httpBackoffMs / 1e3}s):`,
726
+ err
727
+ );
728
+ }
729
+ } finally {
730
+ clearTimeout(timeout);
731
+ }
732
+ }
733
+ /** Fire-and-forget flush using fetch with keepalive (works during page unload) */
734
+ flushSync() {
735
+ if (this.buffer.length === 0) return;
736
+ const logs = this.buffer.splice(0, this.buffer.length);
737
+ try {
738
+ const headers = {
739
+ "Content-Type": "application/json",
740
+ "X-Batch-Id": generateBatchId(),
741
+ "X-SDK-Version": SDK_VERSION
742
+ };
743
+ if (this.config.apiKey) {
744
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
745
+ }
746
+ fetch(this.httpIngestUrl, {
747
+ method: "POST",
748
+ headers,
749
+ body: safeStringify(logs),
750
+ keepalive: true
751
+ });
752
+ } catch {
753
+ }
754
+ }
755
+ registerLifecycleHooks() {
756
+ if (typeof window !== "undefined") {
757
+ this._onVisibilityChange = () => {
758
+ if (document.visibilityState === "hidden") {
759
+ this.flushSync();
760
+ }
761
+ };
762
+ document.addEventListener("visibilitychange", this._onVisibilityChange);
763
+ this._onBeforeUnload = () => {
764
+ this.flushSync();
765
+ };
766
+ window.addEventListener("beforeunload", this._onBeforeUnload);
767
+ }
768
+ }
769
+ /** Check server connectivity and API key validity */
770
+ async ping() {
771
+ const start = Date.now();
772
+ const controller = new AbortController();
773
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
774
+ try {
775
+ const headers = {};
776
+ if (this.config.apiKey) {
777
+ headers["Authorization"] = `Bearer ${this.config.apiKey}`;
778
+ }
779
+ const response = await fetch(`${this.baseUrl}/health`, {
780
+ headers,
781
+ signal: controller.signal
782
+ });
783
+ const latencyMs = Date.now() - start;
784
+ if (!response.ok) {
785
+ const body = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
786
+ return { ok: false, latencyMs, error: body.error || `HTTP ${response.status}` };
787
+ }
788
+ return { ok: true, latencyMs };
789
+ } catch (err) {
790
+ return { ok: false, latencyMs: Date.now() - start, error: err.message };
791
+ } finally {
792
+ clearTimeout(timeout);
793
+ }
794
+ }
795
+ /** Get current connection status */
796
+ getConnectionStatus() {
797
+ if (this.wsState === "connected") {
798
+ return { transport: "websocket", state: "connected" };
799
+ }
800
+ return { transport: "http", state: "connected" };
801
+ }
802
+ };
803
+ _Logger.CONSOLE_METHOD = {
804
+ DEBUG: "debug",
805
+ INFO: "info",
806
+ WARN: "warn",
807
+ ERROR: "error",
808
+ FATAL: "error"
809
+ };
810
+ var Logger = _Logger;
811
+ var ScopedLogger = class _ScopedLogger {
812
+ constructor(parent, group, traceId, watcherId) {
813
+ this.parent = parent;
814
+ this.group = group;
815
+ this.traceId = traceId;
816
+ this.watcherId = watcherId;
817
+ }
818
+ withGroup(group) {
819
+ return new _ScopedLogger(this.parent, group, this.traceId, this.watcherId);
820
+ }
821
+ withTraceId(traceId) {
822
+ return new _ScopedLogger(this.parent, this.group, traceId, this.watcherId);
823
+ }
824
+ withWatcher(watcherId) {
825
+ return new _ScopedLogger(this.parent, this.group, this.traceId, watcherId);
826
+ }
827
+ opts(message, metadata) {
828
+ return {
829
+ message,
830
+ ...this.group && { group: this.group },
831
+ ...this.traceId && { traceId: this.traceId },
832
+ ...this.watcherId && { watcherId: this.watcherId },
833
+ ...metadata && { metadata }
834
+ };
835
+ }
836
+ debug(message, metadata) {
837
+ this.parent.debug(this.opts(message, metadata));
838
+ }
839
+ info(message, metadata) {
840
+ this.parent.info(this.opts(message, metadata));
841
+ }
842
+ warn(message, metadata) {
843
+ this.parent.warn(this.opts(message, metadata));
844
+ }
845
+ error(message, metadata) {
846
+ this.parent.error(this.opts(message, metadata));
847
+ }
848
+ fatal(message, metadata) {
849
+ this.parent.fatal(this.opts(message, metadata));
850
+ }
851
+ };
852
+ function createLogger(config) {
853
+ return new Logger(config);
854
+ }
855
+ var index_default = createLogger;
856
+ export {
857
+ Logger,
858
+ createLogger,
859
+ index_default as default
860
+ };