@statly/observe 1.0.0 → 1.2.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.js CHANGED
@@ -1,8 +1,13 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
6
11
  var __export = (target, all) => {
7
12
  for (var name in all)
8
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,29 +20,215 @@ var __copyProps = (to, from, except, desc) => {
15
20
  }
16
21
  return to;
17
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
18
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
32
 
33
+ // src/span.ts
34
+ var Span, TraceContext;
35
+ var init_span = __esm({
36
+ "src/span.ts"() {
37
+ "use strict";
38
+ Span = class {
39
+ constructor(name, context, tags) {
40
+ this._status = "ok" /* OK */;
41
+ this._tags = {};
42
+ this._metadata = {};
43
+ this._finished = false;
44
+ this.name = name;
45
+ this.context = context;
46
+ this.startTime = Date.now();
47
+ if (tags) this._tags = { ...tags };
48
+ }
49
+ /**
50
+ * Finish the span and calculate duration
51
+ */
52
+ finish(endTime) {
53
+ if (this._finished) return;
54
+ this._endTime = endTime || Date.now();
55
+ this._durationMs = this._endTime - this.startTime;
56
+ this._finished = true;
57
+ }
58
+ setTag(key, value) {
59
+ this._tags[key] = value;
60
+ return this;
61
+ }
62
+ setMetadata(key, value) {
63
+ this._metadata[key] = value;
64
+ return this;
65
+ }
66
+ setStatus(status) {
67
+ this._status = status;
68
+ return this;
69
+ }
70
+ get status() {
71
+ return this._status;
72
+ }
73
+ get tags() {
74
+ return { ...this._tags };
75
+ }
76
+ get durationMs() {
77
+ return this._durationMs;
78
+ }
79
+ toDict() {
80
+ return {
81
+ name: this.name,
82
+ traceId: this.context.traceId,
83
+ spanId: this.context.spanId,
84
+ parentId: this.context.parentId,
85
+ startTime: this.startTime,
86
+ endTime: this._endTime,
87
+ durationMs: this._durationMs,
88
+ status: this._status,
89
+ tags: this._tags,
90
+ metadata: this._metadata
91
+ };
92
+ }
93
+ };
94
+ TraceContext = class {
95
+ static getActiveSpan() {
96
+ return this.currentSpan;
97
+ }
98
+ static setActiveSpan(span) {
99
+ this.currentSpan = span;
100
+ }
101
+ };
102
+ TraceContext.currentSpan = null;
103
+ }
104
+ });
105
+
106
+ // src/telemetry.ts
107
+ var telemetry_exports = {};
108
+ __export(telemetry_exports, {
109
+ TelemetryProvider: () => TelemetryProvider,
110
+ trace: () => trace
111
+ });
112
+ async function trace(name, operation, tags) {
113
+ const provider = TelemetryProvider.getInstance();
114
+ const span = provider.startSpan(name, tags);
115
+ try {
116
+ const result = await operation(span);
117
+ return result;
118
+ } catch (error2) {
119
+ span.setStatus("error" /* ERROR */);
120
+ span.setTag("error", "true");
121
+ if (error2 instanceof Error) {
122
+ span.setTag("exception.type", error2.name);
123
+ span.setTag("exception.message", error2.message);
124
+ }
125
+ throw error2;
126
+ } finally {
127
+ provider.finishSpan(span);
128
+ }
129
+ }
130
+ var TelemetryProvider;
131
+ var init_telemetry = __esm({
132
+ "src/telemetry.ts"() {
133
+ "use strict";
134
+ init_span();
135
+ TelemetryProvider = class _TelemetryProvider {
136
+ constructor() {
137
+ this.client = null;
138
+ }
139
+ static getInstance() {
140
+ if (!_TelemetryProvider.instance) {
141
+ _TelemetryProvider.instance = new _TelemetryProvider();
142
+ }
143
+ return _TelemetryProvider.instance;
144
+ }
145
+ setClient(client2) {
146
+ this.client = client2;
147
+ }
148
+ /**
149
+ * Start a new span
150
+ */
151
+ startSpan(name, tags) {
152
+ const parent = TraceContext.getActiveSpan();
153
+ const traceId = parent ? parent.context.traceId : this.generateId();
154
+ const parentId = parent ? parent.context.spanId : null;
155
+ const span = new Span(name, {
156
+ traceId,
157
+ spanId: this.generateId(),
158
+ parentId
159
+ }, tags);
160
+ TraceContext.setActiveSpan(span);
161
+ return span;
162
+ }
163
+ /**
164
+ * Finish and report a span
165
+ */
166
+ finishSpan(span) {
167
+ span.finish();
168
+ if (TraceContext.getActiveSpan() === span) {
169
+ TraceContext.setActiveSpan(null);
170
+ }
171
+ if (this.client) {
172
+ this.client.captureSpan(span);
173
+ }
174
+ }
175
+ generateId() {
176
+ return Math.random().toString(16).substring(2, 18);
177
+ }
178
+ };
179
+ }
180
+ });
181
+
20
182
  // src/index.ts
21
183
  var index_exports = {};
22
184
  __export(index_exports, {
185
+ AIFeatures: () => AIFeatures,
186
+ ConsoleDestination: () => ConsoleDestination,
187
+ DEFAULT_LEVELS: () => DEFAULT_LEVELS,
188
+ EXTENDED_LEVELS: () => EXTENDED_LEVELS,
189
+ FileDestination: () => FileDestination,
190
+ LOG_LEVELS: () => LOG_LEVELS,
191
+ Logger: () => Logger,
192
+ ObserveDestination: () => ObserveDestination,
193
+ REDACTED: () => REDACTED,
194
+ SCRUB_PATTERNS: () => SCRUB_PATTERNS,
195
+ SENSITIVE_KEYS: () => SENSITIVE_KEYS,
196
+ Scrubber: () => Scrubber,
23
197
  Statly: () => Statly,
24
198
  StatlyClient: () => StatlyClient,
25
199
  addBreadcrumb: () => addBreadcrumb,
26
200
  captureException: () => captureException,
27
201
  captureMessage: () => captureMessage,
28
202
  captureNextJsError: () => captureNextJsError,
203
+ captureSpan: () => captureSpan,
29
204
  close: () => close,
30
205
  createRequestCapture: () => createRequestCapture,
31
206
  expressErrorHandler: () => expressErrorHandler,
32
207
  flush: () => flush,
208
+ formatJson: () => formatJson,
209
+ formatJsonPretty: () => formatJsonPretty,
210
+ formatPretty: () => formatPretty,
33
211
  getClient: () => getClient,
212
+ getConsoleMethod: () => getConsoleMethod,
213
+ getDefaultLogger: () => getDefaultLogger,
34
214
  init: () => init,
215
+ isSensitiveKey: () => isSensitiveKey,
216
+ logAudit: () => audit,
217
+ logDebug: () => debug,
218
+ logError: () => error,
219
+ logFatal: () => fatal,
220
+ logInfo: () => info,
221
+ logTrace: () => trace2,
222
+ logWarn: () => warn,
35
223
  requestHandler: () => requestHandler,
224
+ setDefaultLogger: () => setDefaultLogger,
36
225
  setTag: () => setTag,
37
226
  setTags: () => setTags,
38
227
  setUser: () => setUser,
228
+ startSpan: () => startSpan,
39
229
  statlyFastifyPlugin: () => statlyFastifyPlugin,
40
230
  statlyPlugin: () => statlyPlugin,
231
+ trace: () => trace3,
41
232
  withStatly: () => withStatly,
42
233
  withStatlyGetServerSideProps: () => withStatlyGetServerSideProps,
43
234
  withStatlyGetStaticProps: () => withStatlyGetStaticProps,
@@ -106,10 +297,10 @@ var Transport = class {
106
297
  this.queue = [];
107
298
  try {
108
299
  await this.sendBatch(events);
109
- } catch (error) {
300
+ } catch (error2) {
110
301
  this.queue = [...events, ...this.queue].slice(0, this.maxQueueSize);
111
302
  if (this.debug) {
112
- console.error("[Statly] Failed to send events:", error);
303
+ console.error("[Statly] Failed to send events:", error2);
113
304
  }
114
305
  } finally {
115
306
  this.isSending = false;
@@ -149,13 +340,13 @@ var Transport = class {
149
340
  console.log(`[Statly] Sent ${events.length} event(s)`);
150
341
  }
151
342
  return { success: true, status: response.status };
152
- } catch (error) {
343
+ } catch (error2) {
153
344
  if (this.debug) {
154
- console.error("[Statly] Network error:", error);
345
+ console.error("[Statly] Network error:", error2);
155
346
  }
156
347
  return {
157
348
  success: false,
158
- error: error instanceof Error ? error.message : "Network error"
349
+ error: error2 instanceof Error ? error2.message : "Network error"
159
350
  };
160
351
  }
161
352
  }
@@ -222,16 +413,16 @@ var GlobalHandlers = class {
222
413
  if (!this.errorCallback) {
223
414
  return;
224
415
  }
225
- let error;
416
+ let error2;
226
417
  if (event.reason instanceof Error) {
227
- error = event.reason;
418
+ error2 = event.reason;
228
419
  } else if (typeof event.reason === "string") {
229
- error = new Error(event.reason);
420
+ error2 = new Error(event.reason);
230
421
  } else {
231
- error = new Error("Unhandled Promise Rejection");
232
- error.reason = event.reason;
422
+ error2 = new Error("Unhandled Promise Rejection");
423
+ error2.reason = event.reason;
233
424
  }
234
- this.errorCallback(error, {
425
+ this.errorCallback(error2, {
235
426
  mechanism: { type: "onunhandledrejection", handled: false }
236
427
  });
237
428
  };
@@ -274,13 +465,13 @@ var GlobalHandlers = class {
274
465
  }
275
466
  installOnError() {
276
467
  this.originalOnError = window.onerror;
277
- window.onerror = (message, source, lineno, colno, error) => {
468
+ window.onerror = (message, source, lineno, colno, error2) => {
278
469
  if (this.originalOnError) {
279
- this.originalOnError.call(window, message, source, lineno, colno, error);
470
+ this.originalOnError.call(window, message, source, lineno, colno, error2);
280
471
  }
281
472
  if (this.errorCallback) {
282
- const errorObj = error || new Error(String(message));
283
- if (!error && source) {
473
+ const errorObj = error2 || new Error(String(message));
474
+ if (!error2 && source) {
284
475
  errorObj.filename = source;
285
476
  errorObj.lineno = lineno;
286
477
  errorObj.colno = colno;
@@ -390,6 +581,7 @@ var ConsoleIntegration = class {
390
581
  };
391
582
 
392
583
  // src/client.ts
584
+ init_telemetry();
393
585
  var SDK_NAME = "@statly/observe-sdk";
394
586
  var SDK_VERSION = "0.1.0";
395
587
  var StatlyClient = class {
@@ -404,6 +596,7 @@ var StatlyClient = class {
404
596
  this.breadcrumbs = new BreadcrumbManager(this.options.maxBreadcrumbs);
405
597
  this.globalHandlers = new GlobalHandlers();
406
598
  this.consoleIntegration = new ConsoleIntegration();
599
+ TelemetryProvider.getInstance().setClient(this);
407
600
  }
408
601
  mergeOptions(options) {
409
602
  return {
@@ -443,8 +636,8 @@ var StatlyClient = class {
443
636
  }
444
637
  this.initialized = true;
445
638
  if (this.options.autoCapture) {
446
- this.globalHandlers.install((error, context) => {
447
- this.captureError(error, context);
639
+ this.globalHandlers.install((error2, context) => {
640
+ this.captureError(error2, context);
448
641
  });
449
642
  }
450
643
  if (this.options.captureConsole) {
@@ -467,15 +660,15 @@ var StatlyClient = class {
467
660
  /**
468
661
  * Capture an exception/error
469
662
  */
470
- captureException(error, context) {
663
+ captureException(error2, context) {
471
664
  let errorObj;
472
- if (error instanceof Error) {
473
- errorObj = error;
474
- } else if (typeof error === "string") {
475
- errorObj = new Error(error);
665
+ if (error2 instanceof Error) {
666
+ errorObj = error2;
667
+ } else if (typeof error2 === "string") {
668
+ errorObj = new Error(error2);
476
669
  } else {
477
670
  errorObj = new Error("Unknown error");
478
- errorObj.originalError = error;
671
+ errorObj.originalError = error2;
479
672
  }
480
673
  return this.captureError(errorObj, context);
481
674
  }
@@ -489,21 +682,45 @@ var StatlyClient = class {
489
682
  });
490
683
  return this.sendEvent(event);
491
684
  }
685
+ /**
686
+ * Capture a completed span
687
+ */
688
+ captureSpan(span) {
689
+ const event = this.buildEvent({
690
+ message: `Span: ${span.name}`,
691
+ level: "span",
692
+ span: span.toDict()
693
+ });
694
+ return this.sendEvent(event);
695
+ }
696
+ /**
697
+ * Start a new tracing span
698
+ */
699
+ startSpan(name, tags) {
700
+ return TelemetryProvider.getInstance().startSpan(name, tags);
701
+ }
702
+ /**
703
+ * Execute a function within a trace span
704
+ */
705
+ async trace(name, operation, tags) {
706
+ const { trace: traceFn } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
707
+ return traceFn(name, operation, tags);
708
+ }
492
709
  /**
493
710
  * Internal method to capture an error
494
711
  */
495
- captureError(error, context) {
712
+ captureError(error2, context) {
496
713
  if (Math.random() > this.options.sampleRate) {
497
714
  return "";
498
715
  }
499
716
  const event = this.buildEvent({
500
- message: error.message,
717
+ message: error2.message,
501
718
  level: "error",
502
- stack: error.stack,
719
+ stack: error2.stack,
503
720
  exception: {
504
- type: error.name,
505
- value: error.message,
506
- stacktrace: this.parseStackTrace(error.stack)
721
+ type: error2.name,
722
+ value: error2.message,
723
+ stacktrace: this.parseStackTrace(error2.stack)
507
724
  },
508
725
  extra: context
509
726
  });
@@ -736,8 +953,8 @@ function requestHandler() {
736
953
  }
737
954
  function expressErrorHandler(options = {}) {
738
955
  return (err, req, res, next) => {
739
- const error = err instanceof Error ? err : new Error(String(err));
740
- if (options.shouldHandleError && !options.shouldHandleError(error)) {
956
+ const error2 = err instanceof Error ? err : new Error(String(err));
957
+ if (options.shouldHandleError && !options.shouldHandleError(error2)) {
741
958
  return next(err);
742
959
  }
743
960
  const context = {
@@ -761,7 +978,7 @@ function expressErrorHandler(options = {}) {
761
978
  if (req.statlyContext?.transactionName) {
762
979
  Statly.setTag("transaction", req.statlyContext.transactionName);
763
980
  }
764
- Statly.captureException(error, context);
981
+ Statly.captureException(error2, context);
765
982
  next(err);
766
983
  };
767
984
  }
@@ -798,73 +1015,88 @@ function sanitizeBody(body) {
798
1015
  // src/integrations/nextjs.ts
799
1016
  function withStatlyPagesApi(handler) {
800
1017
  return async (req, res) => {
801
- Statly.addBreadcrumb({
802
- category: "http",
803
- message: `${req.method} ${req.url}`,
804
- level: "info",
805
- data: {
806
- method: req.method,
807
- url: req.url
808
- }
809
- });
810
- try {
811
- return await handler(req, res);
812
- } catch (error) {
813
- const context = {
814
- request: {
1018
+ return Statly.trace(`${req.method} ${req.url}`, async (span) => {
1019
+ span.setTag("component", "nextjs-pages-api");
1020
+ span.setTag("http.method", req.method || "GET");
1021
+ span.setTag("http.url", req.url || "unknown");
1022
+ Statly.addBreadcrumb({
1023
+ category: "http",
1024
+ message: `${req.method} ${req.url}`,
1025
+ level: "info",
1026
+ data: {
815
1027
  method: req.method,
816
- url: req.url,
817
- headers: sanitizeHeaders2(req.headers),
818
- query: req.query
1028
+ url: req.url
819
1029
  }
820
- };
821
- Statly.captureException(error, context);
822
- throw error;
823
- }
1030
+ });
1031
+ try {
1032
+ const result = await handler(req, res);
1033
+ return result;
1034
+ } catch (error2) {
1035
+ const context = {
1036
+ request: {
1037
+ method: req.method,
1038
+ url: req.url,
1039
+ headers: sanitizeHeaders2(req.headers),
1040
+ query: req.query
1041
+ }
1042
+ };
1043
+ Statly.captureException(error2, context);
1044
+ throw error2;
1045
+ }
1046
+ });
824
1047
  };
825
1048
  }
826
1049
  function withStatly(handler) {
827
1050
  const wrappedHandler = async (request, context) => {
828
- Statly.addBreadcrumb({
829
- category: "http",
830
- message: `${request.method} ${request.nextUrl?.pathname || request.url}`,
831
- level: "info",
832
- data: {
833
- method: request.method,
834
- url: request.nextUrl?.pathname || request.url
835
- }
836
- });
837
- try {
838
- return await handler(request, context);
839
- } catch (error) {
840
- const headers = {};
841
- request.headers.forEach((value, key) => {
842
- headers[key] = value;
843
- });
844
- const errorContext = {
845
- request: {
1051
+ return Statly.trace(`${request.method} ${request.nextUrl?.pathname || request.url}`, async (span) => {
1052
+ span.setTag("component", "nextjs-app-router");
1053
+ span.setTag("http.method", request.method);
1054
+ span.setTag("http.url", request.nextUrl?.pathname || request.url);
1055
+ Statly.addBreadcrumb({
1056
+ category: "http",
1057
+ message: `${request.method} ${request.nextUrl?.pathname || request.url}`,
1058
+ level: "info",
1059
+ data: {
846
1060
  method: request.method,
847
- url: request.nextUrl?.pathname || request.url,
848
- headers: sanitizeHeaders2(headers),
849
- searchParams: request.nextUrl?.searchParams?.toString()
1061
+ url: request.nextUrl?.pathname || request.url
850
1062
  }
851
- };
852
- if (context?.params) {
853
- try {
854
- errorContext.params = await context.params;
855
- } catch {
1063
+ });
1064
+ try {
1065
+ const result = await handler(request, context);
1066
+ if (result instanceof Response) {
1067
+ span.setTag("http.status_code", result.status.toString());
1068
+ }
1069
+ return result;
1070
+ } catch (error2) {
1071
+ const headers = {};
1072
+ request.headers.forEach((value, key) => {
1073
+ headers[key] = value;
1074
+ });
1075
+ const errorContext = {
1076
+ request: {
1077
+ method: request.method,
1078
+ url: request.nextUrl?.pathname || request.url,
1079
+ headers: sanitizeHeaders2(headers),
1080
+ searchParams: request.nextUrl?.searchParams?.toString()
1081
+ }
1082
+ };
1083
+ if (context?.params) {
1084
+ try {
1085
+ errorContext.params = await context.params;
1086
+ } catch {
1087
+ }
856
1088
  }
1089
+ Statly.captureException(error2, errorContext);
1090
+ throw error2;
857
1091
  }
858
- Statly.captureException(error, errorContext);
859
- throw error;
860
- }
1092
+ });
861
1093
  };
862
1094
  return wrappedHandler;
863
1095
  }
864
- function captureNextJsError(error, context) {
865
- return Statly.captureException(error, {
1096
+ function captureNextJsError(error2, context) {
1097
+ return Statly.captureException(error2, {
866
1098
  ...context,
867
- digest: error.digest,
1099
+ digest: error2.digest,
868
1100
  source: "nextjs-error-boundary"
869
1101
  });
870
1102
  }
@@ -872,12 +1104,12 @@ function withStatlyGetServerSideProps(handler) {
872
1104
  return async (context) => {
873
1105
  try {
874
1106
  return await handler(context);
875
- } catch (error) {
876
- Statly.captureException(error, {
1107
+ } catch (error2) {
1108
+ Statly.captureException(error2, {
877
1109
  source: "getServerSideProps",
878
1110
  url: context.req?.url || context.resolvedUrl
879
1111
  });
880
- throw error;
1112
+ throw error2;
881
1113
  }
882
1114
  };
883
1115
  }
@@ -885,31 +1117,35 @@ function withStatlyGetStaticProps(handler) {
885
1117
  return async (context) => {
886
1118
  try {
887
1119
  return await handler(context);
888
- } catch (error) {
889
- Statly.captureException(error, {
1120
+ } catch (error2) {
1121
+ Statly.captureException(error2, {
890
1122
  source: "getStaticProps",
891
1123
  params: context.params
892
1124
  });
893
- throw error;
1125
+ throw error2;
894
1126
  }
895
1127
  };
896
1128
  }
897
1129
  function withStatlyServerAction(action, actionName) {
898
1130
  return async (...args) => {
899
- Statly.addBreadcrumb({
900
- category: "action",
901
- message: `Server action: ${actionName || "unknown"}`,
902
- level: "info"
903
- });
904
- try {
905
- return await action(...args);
906
- } catch (error) {
907
- Statly.captureException(error, {
908
- source: "server-action",
909
- actionName
1131
+ return Statly.trace(`Action: ${actionName || "unknown"}`, async (span) => {
1132
+ span.setTag("component", "nextjs-server-action");
1133
+ span.setTag("action.name", actionName || "unknown");
1134
+ Statly.addBreadcrumb({
1135
+ category: "action",
1136
+ message: `Server action: ${actionName || "unknown"}`,
1137
+ level: "info"
910
1138
  });
911
- throw error;
912
- }
1139
+ try {
1140
+ return await action(...args);
1141
+ } catch (error2) {
1142
+ Statly.captureException(error2, {
1143
+ source: "server-action",
1144
+ actionName
1145
+ });
1146
+ throw error2;
1147
+ }
1148
+ });
913
1149
  };
914
1150
  }
915
1151
  function sanitizeHeaders2(headers) {
@@ -962,16 +1198,16 @@ function statlyFastifyPlugin(fastify, options, done) {
962
1198
  });
963
1199
  hookDone();
964
1200
  });
965
- fastify.setErrorHandler((error, request, reply) => {
966
- const statusCode = error.statusCode || 500;
1201
+ fastify.setErrorHandler((error2, request, reply) => {
1202
+ const statusCode = error2.statusCode || 500;
967
1203
  if (skipStatusCodes.includes(statusCode)) {
968
- throw error;
1204
+ throw error2;
969
1205
  }
970
- if (!captureValidationErrors && error.validation) {
971
- throw error;
1206
+ if (!captureValidationErrors && error2.validation) {
1207
+ throw error2;
972
1208
  }
973
- if (shouldCapture && !shouldCapture(error)) {
974
- throw error;
1209
+ if (shouldCapture && !shouldCapture(error2)) {
1210
+ throw error2;
975
1211
  }
976
1212
  const context = {
977
1213
  request: {
@@ -984,27 +1220,27 @@ function statlyFastifyPlugin(fastify, options, done) {
984
1220
  params: request.params
985
1221
  },
986
1222
  error: {
987
- statusCode: error.statusCode,
988
- code: error.code
1223
+ statusCode: error2.statusCode,
1224
+ code: error2.code
989
1225
  }
990
1226
  };
991
1227
  if (request.ip) {
992
1228
  context.ip = request.ip;
993
1229
  }
994
- if (error.validation) {
995
- context.validation = error.validation;
1230
+ if (error2.validation) {
1231
+ context.validation = error2.validation;
996
1232
  }
997
1233
  Statly.setTag("http.method", request.method);
998
1234
  Statly.setTag("http.url", request.routerPath || request.url);
999
1235
  Statly.setTag("http.status_code", String(statusCode));
1000
- Statly.captureException(error, context);
1001
- throw error;
1236
+ Statly.captureException(error2, context);
1237
+ throw error2;
1002
1238
  });
1003
1239
  done();
1004
1240
  }
1005
1241
  var statlyPlugin = statlyFastifyPlugin;
1006
1242
  function createRequestCapture(request) {
1007
- return (error, additionalContext) => {
1243
+ return (error2, additionalContext) => {
1008
1244
  const context = {
1009
1245
  request: {
1010
1246
  id: request.id,
@@ -1014,7 +1250,7 @@ function createRequestCapture(request) {
1014
1250
  },
1015
1251
  ...additionalContext
1016
1252
  };
1017
- return Statly.captureException(error, context);
1253
+ return Statly.captureException(error2, context);
1018
1254
  };
1019
1255
  }
1020
1256
  function sanitizeHeaders3(headers) {
@@ -1030,130 +1266,1570 @@ function sanitizeHeaders3(headers) {
1030
1266
  return sanitized;
1031
1267
  }
1032
1268
 
1033
- // src/index.ts
1034
- var client = null;
1035
- function loadDsnFromEnv() {
1036
- if (typeof process !== "undefined" && process.env) {
1037
- return process.env.STATLY_DSN || process.env.NEXT_PUBLIC_STATLY_DSN || process.env.STATLY_OBSERVE_DSN;
1269
+ // src/logger/types.ts
1270
+ var LOG_LEVELS = {
1271
+ trace: 0,
1272
+ debug: 1,
1273
+ info: 2,
1274
+ warn: 3,
1275
+ error: 4,
1276
+ fatal: 5,
1277
+ audit: 6
1278
+ // Special: always logged, never sampled
1279
+ };
1280
+ var DEFAULT_LEVELS = ["debug", "info", "warn", "error", "fatal"];
1281
+ var EXTENDED_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal", "audit"];
1282
+
1283
+ // src/logger/scrubbing/patterns.ts
1284
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
1285
+ "password",
1286
+ "passwd",
1287
+ "pwd",
1288
+ "secret",
1289
+ "api_key",
1290
+ "apikey",
1291
+ "api-key",
1292
+ "token",
1293
+ "access_token",
1294
+ "accesstoken",
1295
+ "refresh_token",
1296
+ "auth",
1297
+ "authorization",
1298
+ "bearer",
1299
+ "credential",
1300
+ "credentials",
1301
+ "private_key",
1302
+ "privatekey",
1303
+ "private-key",
1304
+ "secret_key",
1305
+ "secretkey",
1306
+ "secret-key",
1307
+ "session_id",
1308
+ "sessionid",
1309
+ "session-id",
1310
+ "session",
1311
+ "cookie",
1312
+ "x-api-key",
1313
+ "x-auth-token",
1314
+ "x-access-token"
1315
+ ]);
1316
+ var SCRUB_PATTERNS = {
1317
+ apiKey: {
1318
+ regex: /(?:api[_-]?key|apikey)\s*[=:]\s*["']?([a-zA-Z0-9_\-]{20,})["']?/gi,
1319
+ description: "API keys in various formats"
1320
+ },
1321
+ password: {
1322
+ regex: /(?:password|passwd|pwd|secret)\s*[=:]\s*["']?([^"'\s]{3,})["']?/gi,
1323
+ description: "Passwords and secrets"
1324
+ },
1325
+ token: {
1326
+ regex: /(?:bearer\s+|token\s*[=:]\s*["']?)([a-zA-Z0-9_\-\.]{20,})["']?/gi,
1327
+ description: "Bearer tokens and auth tokens"
1328
+ },
1329
+ creditCard: {
1330
+ // Visa, Mastercard, Amex, Discover, etc.
1331
+ regex: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b/g,
1332
+ description: "Credit card numbers"
1333
+ },
1334
+ ssn: {
1335
+ regex: /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/g,
1336
+ description: "US Social Security Numbers"
1337
+ },
1338
+ email: {
1339
+ regex: /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g,
1340
+ description: "Email addresses"
1341
+ },
1342
+ ipAddress: {
1343
+ regex: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g,
1344
+ description: "IPv4 addresses"
1345
+ },
1346
+ awsKey: {
1347
+ regex: /(?:AKIA|ABIA|ACCA)[A-Z0-9]{16}/g,
1348
+ description: "AWS Access Key IDs"
1349
+ },
1350
+ privateKey: {
1351
+ regex: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
1352
+ description: "Private keys in PEM format"
1353
+ },
1354
+ jwt: {
1355
+ regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
1356
+ description: "JSON Web Tokens"
1038
1357
  }
1039
- return void 0;
1358
+ };
1359
+ var REDACTED = "[REDACTED]";
1360
+ function isSensitiveKey(key) {
1361
+ const lowerKey = key.toLowerCase();
1362
+ return SENSITIVE_KEYS.has(lowerKey);
1040
1363
  }
1041
- function loadEnvironmentFromEnv() {
1042
- if (typeof process !== "undefined" && process.env) {
1043
- return process.env.STATLY_ENVIRONMENT || process.env.NODE_ENV;
1364
+
1365
+ // src/logger/scrubbing/scrubber.ts
1366
+ var Scrubber = class {
1367
+ constructor(config = {}) {
1368
+ this.enabled = config.enabled !== false;
1369
+ this.patterns = /* @__PURE__ */ new Map();
1370
+ this.customPatterns = config.customPatterns || [];
1371
+ this.allowlist = new Set((config.allowlist || []).map((k) => k.toLowerCase()));
1372
+ this.customScrubber = config.customScrubber;
1373
+ const patternNames = config.patterns || [
1374
+ "apiKey",
1375
+ "password",
1376
+ "token",
1377
+ "creditCard",
1378
+ "ssn",
1379
+ "awsKey",
1380
+ "privateKey",
1381
+ "jwt"
1382
+ ];
1383
+ for (const name of patternNames) {
1384
+ const pattern = SCRUB_PATTERNS[name];
1385
+ if (pattern) {
1386
+ this.patterns.set(name, new RegExp(pattern.regex.source, pattern.regex.flags));
1387
+ }
1388
+ }
1044
1389
  }
1045
- return void 0;
1046
- }
1047
- function init(options) {
1048
- if (client) {
1049
- console.warn("[Statly] SDK already initialized. Call close() first to reinitialize.");
1050
- return;
1390
+ /**
1391
+ * Scrub sensitive data from a value
1392
+ */
1393
+ scrub(value) {
1394
+ if (!this.enabled) {
1395
+ return value;
1396
+ }
1397
+ return this.scrubValue(value, "");
1051
1398
  }
1052
- const dsn = options?.dsn || loadDsnFromEnv();
1053
- if (!dsn) {
1054
- console.error("[Statly] No DSN provided. Set STATLY_DSN in your environment or pass dsn to init().");
1055
- console.error("[Statly] Get your DSN at https://statly.live/dashboard/observe/setup");
1056
- return;
1399
+ /**
1400
+ * Scrub a log message string
1401
+ */
1402
+ scrubMessage(message) {
1403
+ if (!this.enabled) {
1404
+ return message;
1405
+ }
1406
+ let result = message;
1407
+ for (const [, regex] of this.patterns) {
1408
+ result = result.replace(regex, REDACTED);
1409
+ }
1410
+ for (const regex of this.customPatterns) {
1411
+ result = result.replace(regex, REDACTED);
1412
+ }
1413
+ return result;
1057
1414
  }
1058
- const environment = options?.environment || loadEnvironmentFromEnv();
1059
- const finalOptions = {
1060
- ...options,
1061
- dsn,
1062
- environment
1063
- };
1064
- client = new StatlyClient(finalOptions);
1065
- client.init();
1066
- }
1067
- function captureException(error, context) {
1068
- if (!client) {
1069
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1070
- return "";
1415
+ /**
1416
+ * Recursively scrub sensitive data
1417
+ */
1418
+ scrubValue(value, key) {
1419
+ if (key && this.allowlist.has(key.toLowerCase())) {
1420
+ return value;
1421
+ }
1422
+ if (this.customScrubber && key) {
1423
+ const result = this.customScrubber(key, value);
1424
+ if (result !== value) {
1425
+ return result;
1426
+ }
1427
+ }
1428
+ if (key && isSensitiveKey(key)) {
1429
+ return REDACTED;
1430
+ }
1431
+ if (value === null || value === void 0) {
1432
+ return value;
1433
+ }
1434
+ if (typeof value === "string") {
1435
+ return this.scrubString(value);
1436
+ }
1437
+ if (Array.isArray(value)) {
1438
+ return value.map((item, index) => this.scrubValue(item, String(index)));
1439
+ }
1440
+ if (typeof value === "object") {
1441
+ return this.scrubObject(value);
1442
+ }
1443
+ return value;
1071
1444
  }
1072
- return client.captureException(error, context);
1073
- }
1074
- function captureMessage(message, level = "info") {
1075
- if (!client) {
1076
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1077
- return "";
1445
+ /**
1446
+ * Scrub sensitive patterns from a string
1447
+ */
1448
+ scrubString(value) {
1449
+ let result = value;
1450
+ for (const [, regex] of this.patterns) {
1451
+ regex.lastIndex = 0;
1452
+ result = result.replace(regex, REDACTED);
1453
+ }
1454
+ for (const regex of this.customPatterns) {
1455
+ const newRegex = new RegExp(regex.source, regex.flags);
1456
+ result = result.replace(newRegex, REDACTED);
1457
+ }
1458
+ return result;
1078
1459
  }
1079
- return client.captureMessage(message, level);
1080
- }
1081
- function setUser(user) {
1082
- if (!client) {
1083
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1084
- return;
1460
+ /**
1461
+ * Scrub sensitive data from an object
1462
+ */
1463
+ scrubObject(obj) {
1464
+ const result = {};
1465
+ for (const [key, value] of Object.entries(obj)) {
1466
+ result[key] = this.scrubValue(value, key);
1467
+ }
1468
+ return result;
1085
1469
  }
1086
- client.setUser(user);
1087
- }
1088
- function setTag(key, value) {
1089
- if (!client) {
1090
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1091
- return;
1470
+ /**
1471
+ * Add a custom pattern at runtime
1472
+ */
1473
+ addPattern(pattern) {
1474
+ this.customPatterns.push(pattern);
1092
1475
  }
1093
- client.setTag(key, value);
1094
- }
1095
- function setTags(tags) {
1096
- if (!client) {
1097
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1098
- return;
1476
+ /**
1477
+ * Add a key to the allowlist
1478
+ */
1479
+ addToAllowlist(key) {
1480
+ this.allowlist.add(key.toLowerCase());
1099
1481
  }
1100
- client.setTags(tags);
1101
- }
1102
- function addBreadcrumb(breadcrumb) {
1103
- if (!client) {
1104
- console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1105
- return;
1482
+ /**
1483
+ * Check if scrubbing is enabled
1484
+ */
1485
+ isEnabled() {
1486
+ return this.enabled;
1106
1487
  }
1107
- client.addBreadcrumb(breadcrumb);
1108
- }
1109
- async function flush() {
1110
- if (!client) {
1111
- return;
1488
+ /**
1489
+ * Enable or disable scrubbing
1490
+ */
1491
+ setEnabled(enabled) {
1492
+ this.enabled = enabled;
1112
1493
  }
1113
- await client.flush();
1114
- }
1115
- async function close() {
1116
- if (!client) {
1117
- return;
1494
+ };
1495
+
1496
+ // src/logger/formatters/console.ts
1497
+ var COLORS = {
1498
+ reset: "\x1B[0m",
1499
+ bold: "\x1B[1m",
1500
+ dim: "\x1B[2m",
1501
+ // Foreground colors
1502
+ black: "\x1B[30m",
1503
+ red: "\x1B[31m",
1504
+ green: "\x1B[32m",
1505
+ yellow: "\x1B[33m",
1506
+ blue: "\x1B[34m",
1507
+ magenta: "\x1B[35m",
1508
+ cyan: "\x1B[36m",
1509
+ white: "\x1B[37m",
1510
+ gray: "\x1B[90m",
1511
+ // Background colors
1512
+ bgRed: "\x1B[41m",
1513
+ bgYellow: "\x1B[43m"
1514
+ };
1515
+ var LEVEL_COLORS = {
1516
+ trace: COLORS.gray,
1517
+ debug: COLORS.cyan,
1518
+ info: COLORS.green,
1519
+ warn: COLORS.yellow,
1520
+ error: COLORS.red,
1521
+ fatal: `${COLORS.bgRed}${COLORS.white}`,
1522
+ audit: COLORS.magenta
1523
+ };
1524
+ var LEVEL_LABELS = {
1525
+ trace: "TRACE",
1526
+ debug: "DEBUG",
1527
+ info: "INFO ",
1528
+ warn: "WARN ",
1529
+ error: "ERROR",
1530
+ fatal: "FATAL",
1531
+ audit: "AUDIT"
1532
+ };
1533
+ function formatPretty(entry, options = {}) {
1534
+ const {
1535
+ colors = true,
1536
+ timestamps = true,
1537
+ showLevel = true,
1538
+ showLogger = true,
1539
+ showContext = true,
1540
+ showSource = false
1541
+ } = options;
1542
+ const parts = [];
1543
+ if (timestamps) {
1544
+ const date = new Date(entry.timestamp);
1545
+ const time = date.toISOString().replace("T", " ").replace("Z", "");
1546
+ parts.push(colors ? `${COLORS.dim}${time}${COLORS.reset}` : time);
1118
1547
  }
1119
- await client.close();
1120
- client = null;
1548
+ if (showLevel) {
1549
+ const levelColor = colors ? LEVEL_COLORS[entry.level] : "";
1550
+ const levelLabel = LEVEL_LABELS[entry.level];
1551
+ parts.push(colors ? `${levelColor}${levelLabel}${COLORS.reset}` : levelLabel);
1552
+ }
1553
+ if (showLogger && entry.loggerName) {
1554
+ parts.push(colors ? `${COLORS.blue}[${entry.loggerName}]${COLORS.reset}` : `[${entry.loggerName}]`);
1555
+ }
1556
+ parts.push(entry.message);
1557
+ if (showSource && entry.source) {
1558
+ const { file, line, function: fn } = entry.source;
1559
+ const loc = [file, line, fn].filter(Boolean).join(":");
1560
+ if (loc) {
1561
+ parts.push(colors ? `${COLORS.dim}(${loc})${COLORS.reset}` : `(${loc})`);
1562
+ }
1563
+ }
1564
+ let result = parts.join(" ");
1565
+ if (showContext && entry.context && Object.keys(entry.context).length > 0) {
1566
+ const contextStr = JSON.stringify(entry.context, null, 2);
1567
+ result += "\n" + (colors ? `${COLORS.dim}${contextStr}${COLORS.reset}` : contextStr);
1568
+ }
1569
+ return result;
1121
1570
  }
1122
- function getClient() {
1123
- return client;
1571
+ function formatJson(entry) {
1572
+ return JSON.stringify(entry);
1124
1573
  }
1125
- var Statly = {
1126
- init,
1127
- captureException,
1128
- captureMessage,
1129
- setUser,
1130
- setTag,
1131
- setTags,
1132
- addBreadcrumb,
1133
- flush,
1134
- close,
1135
- getClient
1574
+ function formatJsonPretty(entry) {
1575
+ return JSON.stringify(entry, null, 2);
1576
+ }
1577
+ function getConsoleMethod(level) {
1578
+ switch (level) {
1579
+ case "trace":
1580
+ return "trace";
1581
+ case "debug":
1582
+ return "debug";
1583
+ case "info":
1584
+ return "info";
1585
+ case "warn":
1586
+ return "warn";
1587
+ case "error":
1588
+ case "fatal":
1589
+ return "error";
1590
+ case "audit":
1591
+ return "info";
1592
+ default:
1593
+ return "log";
1594
+ }
1595
+ }
1596
+
1597
+ // src/logger/destinations/console.ts
1598
+ var ConsoleDestination = class {
1599
+ constructor(config = {}) {
1600
+ this.name = "console";
1601
+ this.config = {
1602
+ enabled: config.enabled !== false,
1603
+ colors: config.colors !== false,
1604
+ format: config.format || "pretty",
1605
+ timestamps: config.timestamps !== false,
1606
+ levels: config.levels || ["trace", "debug", "info", "warn", "error", "fatal", "audit"]
1607
+ };
1608
+ this.minLevel = 0;
1609
+ }
1610
+ /**
1611
+ * Write a log entry to the console
1612
+ */
1613
+ write(entry) {
1614
+ if (!this.config.enabled) {
1615
+ return;
1616
+ }
1617
+ if (!this.config.levels.includes(entry.level)) {
1618
+ return;
1619
+ }
1620
+ if (LOG_LEVELS[entry.level] < this.minLevel) {
1621
+ return;
1622
+ }
1623
+ let output;
1624
+ if (this.config.format === "json") {
1625
+ output = formatJson(entry);
1626
+ } else {
1627
+ output = formatPretty(entry, {
1628
+ colors: this.config.colors && this.supportsColors(),
1629
+ timestamps: this.config.timestamps
1630
+ });
1631
+ }
1632
+ const method = getConsoleMethod(entry.level);
1633
+ console[method](output);
1634
+ }
1635
+ /**
1636
+ * Check if the environment supports colors
1637
+ */
1638
+ supportsColors() {
1639
+ if (typeof window !== "undefined") {
1640
+ return true;
1641
+ }
1642
+ if (typeof process !== "undefined") {
1643
+ if (process.stdout && "isTTY" in process.stdout) {
1644
+ return Boolean(process.stdout.isTTY);
1645
+ }
1646
+ const env = process.env;
1647
+ if (env.FORCE_COLOR !== void 0) {
1648
+ return env.FORCE_COLOR !== "0";
1649
+ }
1650
+ if (env.NO_COLOR !== void 0) {
1651
+ return false;
1652
+ }
1653
+ if (env.TERM === "dumb") {
1654
+ return false;
1655
+ }
1656
+ return true;
1657
+ }
1658
+ return false;
1659
+ }
1660
+ /**
1661
+ * Set minimum log level
1662
+ */
1663
+ setMinLevel(level) {
1664
+ this.minLevel = LOG_LEVELS[level];
1665
+ }
1666
+ /**
1667
+ * Enable or disable the destination
1668
+ */
1669
+ setEnabled(enabled) {
1670
+ this.config.enabled = enabled;
1671
+ }
1672
+ /**
1673
+ * Set color mode
1674
+ */
1675
+ setColors(enabled) {
1676
+ this.config.colors = enabled;
1677
+ }
1678
+ /**
1679
+ * Set output format
1680
+ */
1681
+ setFormat(format) {
1682
+ this.config.format = format;
1683
+ }
1684
+ };
1685
+
1686
+ // src/logger/destinations/observe.ts
1687
+ var DEFAULT_BATCH_SIZE = 50;
1688
+ var DEFAULT_FLUSH_INTERVAL = 5e3;
1689
+ var DEFAULT_SAMPLING = {
1690
+ trace: 0.01,
1691
+ // 1%
1692
+ debug: 0.1,
1693
+ // 10%
1694
+ info: 0.5,
1695
+ // 50%
1696
+ warn: 1,
1697
+ // 100%
1698
+ error: 1,
1699
+ // 100%
1700
+ fatal: 1,
1701
+ // 100%
1702
+ audit: 1
1703
+ // 100% - never sampled
1704
+ };
1705
+ var ObserveDestination = class {
1706
+ constructor(dsn, config = {}) {
1707
+ this.name = "observe";
1708
+ this.queue = [];
1709
+ this.isFlushing = false;
1710
+ this.minLevel = 0;
1711
+ this.dsn = dsn;
1712
+ this.endpoint = this.parseEndpoint(dsn);
1713
+ this.config = {
1714
+ enabled: config.enabled !== false,
1715
+ batchSize: config.batchSize || DEFAULT_BATCH_SIZE,
1716
+ flushInterval: config.flushInterval || DEFAULT_FLUSH_INTERVAL,
1717
+ sampling: { ...DEFAULT_SAMPLING, ...config.sampling },
1718
+ levels: config.levels || ["trace", "debug", "info", "warn", "error", "fatal", "audit"]
1719
+ };
1720
+ this.startFlushTimer();
1721
+ }
1722
+ /**
1723
+ * Parse DSN to construct endpoint
1724
+ */
1725
+ parseEndpoint(dsn) {
1726
+ try {
1727
+ const url = new URL(dsn);
1728
+ return `${url.protocol}//${url.host}/api/v1/logs/ingest`;
1729
+ } catch {
1730
+ return "https://statly.live/api/v1/logs/ingest";
1731
+ }
1732
+ }
1733
+ /**
1734
+ * Start the flush timer
1735
+ */
1736
+ startFlushTimer() {
1737
+ if (this.flushTimer) {
1738
+ clearInterval(this.flushTimer);
1739
+ }
1740
+ if (typeof setInterval !== "undefined") {
1741
+ this.flushTimer = setInterval(() => {
1742
+ this.flush();
1743
+ }, this.config.flushInterval);
1744
+ }
1745
+ }
1746
+ /**
1747
+ * Write a log entry (queues for batching)
1748
+ */
1749
+ write(entry) {
1750
+ if (!this.config.enabled) {
1751
+ return;
1752
+ }
1753
+ if (!this.config.levels.includes(entry.level)) {
1754
+ return;
1755
+ }
1756
+ if (LOG_LEVELS[entry.level] < this.minLevel) {
1757
+ return;
1758
+ }
1759
+ if (entry.level !== "audit") {
1760
+ const sampleRate = this.config.sampling[entry.level] ?? 1;
1761
+ if (Math.random() > sampleRate) {
1762
+ return;
1763
+ }
1764
+ }
1765
+ this.queue.push(entry);
1766
+ if (this.queue.length >= this.config.batchSize) {
1767
+ this.flush();
1768
+ }
1769
+ }
1770
+ /**
1771
+ * Flush all queued entries to the server
1772
+ */
1773
+ async flush() {
1774
+ if (this.isFlushing || this.queue.length === 0) {
1775
+ return;
1776
+ }
1777
+ this.isFlushing = true;
1778
+ const entries = [...this.queue];
1779
+ this.queue = [];
1780
+ try {
1781
+ await this.sendBatch(entries);
1782
+ } catch (error2) {
1783
+ const maxQueue = this.config.batchSize * 3;
1784
+ this.queue = [...entries, ...this.queue].slice(0, maxQueue);
1785
+ console.error("[Statly Logger] Failed to send logs:", error2);
1786
+ } finally {
1787
+ this.isFlushing = false;
1788
+ }
1789
+ }
1790
+ /**
1791
+ * Send a batch of entries to the server
1792
+ */
1793
+ async sendBatch(entries) {
1794
+ if (entries.length === 0) {
1795
+ return;
1796
+ }
1797
+ const response = await fetch(this.endpoint, {
1798
+ method: "POST",
1799
+ headers: {
1800
+ "Content-Type": "application/json",
1801
+ "X-Statly-DSN": this.dsn
1802
+ },
1803
+ body: JSON.stringify({ logs: entries }),
1804
+ keepalive: true
1805
+ });
1806
+ if (!response.ok) {
1807
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1808
+ }
1809
+ }
1810
+ /**
1811
+ * Close the destination
1812
+ */
1813
+ async close() {
1814
+ if (this.flushTimer) {
1815
+ clearInterval(this.flushTimer);
1816
+ }
1817
+ await this.flush();
1818
+ }
1819
+ /**
1820
+ * Set minimum log level
1821
+ */
1822
+ setMinLevel(level) {
1823
+ this.minLevel = LOG_LEVELS[level];
1824
+ }
1825
+ /**
1826
+ * Set sampling rate for a level
1827
+ */
1828
+ setSamplingRate(level, rate) {
1829
+ this.config.sampling[level] = Math.max(0, Math.min(1, rate));
1830
+ }
1831
+ /**
1832
+ * Enable or disable the destination
1833
+ */
1834
+ setEnabled(enabled) {
1835
+ this.config.enabled = enabled;
1836
+ }
1837
+ /**
1838
+ * Get the current queue size
1839
+ */
1840
+ getQueueSize() {
1841
+ return this.queue.length;
1842
+ }
1843
+ };
1844
+
1845
+ // src/logger/destinations/file.ts
1846
+ function parseSize(size) {
1847
+ const match = size.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i);
1848
+ if (!match) return 10 * 1024 * 1024;
1849
+ const value = parseFloat(match[1]);
1850
+ const unit = (match[2] || "B").toUpperCase();
1851
+ switch (unit) {
1852
+ case "KB":
1853
+ return value * 1024;
1854
+ case "MB":
1855
+ return value * 1024 * 1024;
1856
+ case "GB":
1857
+ return value * 1024 * 1024 * 1024;
1858
+ default:
1859
+ return value;
1860
+ }
1861
+ }
1862
+ function formatDate(date) {
1863
+ return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
1864
+ }
1865
+ var FileDestination = class {
1866
+ constructor(config) {
1867
+ this.name = "file";
1868
+ this.minLevel = 0;
1869
+ this.buffer = [];
1870
+ this.currentSize = 0;
1871
+ this.writePromise = Promise.resolve();
1872
+ // File system operations (injected for Node.js compatibility)
1873
+ this.fs = null;
1874
+ this.config = {
1875
+ enabled: config.enabled !== false,
1876
+ path: config.path,
1877
+ format: config.format || "json",
1878
+ rotation: config.rotation || { type: "size", maxSize: "10MB", maxFiles: 5 },
1879
+ levels: config.levels || ["trace", "debug", "info", "warn", "error", "fatal", "audit"]
1880
+ };
1881
+ this.maxSize = parseSize(this.config.rotation.maxSize || "10MB");
1882
+ this.lastRotation = /* @__PURE__ */ new Date();
1883
+ this.rotationInterval = this.getRotationInterval();
1884
+ this.initFileSystem();
1885
+ }
1886
+ /**
1887
+ * Initialize file system operations
1888
+ */
1889
+ async initFileSystem() {
1890
+ if (typeof process !== "undefined" && process.versions?.node) {
1891
+ try {
1892
+ const fs = await import("fs/promises");
1893
+ const path = await import("path");
1894
+ const fsOps = {
1895
+ appendFile: fs.appendFile,
1896
+ rename: fs.rename,
1897
+ stat: fs.stat,
1898
+ mkdir: (p, opts) => fs.mkdir(p, opts),
1899
+ readdir: fs.readdir,
1900
+ unlink: fs.unlink
1901
+ };
1902
+ this.fs = fsOps;
1903
+ const dir = path.dirname(this.config.path);
1904
+ await fsOps.mkdir(dir, { recursive: true });
1905
+ } catch {
1906
+ console.warn("[Statly Logger] File destination not available (not Node.js)");
1907
+ this.config.enabled = false;
1908
+ }
1909
+ } else {
1910
+ this.config.enabled = false;
1911
+ }
1912
+ }
1913
+ /**
1914
+ * Get rotation interval in milliseconds
1915
+ */
1916
+ getRotationInterval() {
1917
+ const { interval } = this.config.rotation;
1918
+ switch (interval) {
1919
+ case "hourly":
1920
+ return 60 * 60 * 1e3;
1921
+ case "daily":
1922
+ return 24 * 60 * 60 * 1e3;
1923
+ case "weekly":
1924
+ return 7 * 24 * 60 * 60 * 1e3;
1925
+ default:
1926
+ return Infinity;
1927
+ }
1928
+ }
1929
+ /**
1930
+ * Write a log entry
1931
+ */
1932
+ write(entry) {
1933
+ if (!this.config.enabled || !this.fs) {
1934
+ return;
1935
+ }
1936
+ if (!this.config.levels.includes(entry.level)) {
1937
+ return;
1938
+ }
1939
+ if (LOG_LEVELS[entry.level] < this.minLevel) {
1940
+ return;
1941
+ }
1942
+ let line;
1943
+ if (this.config.format === "json") {
1944
+ line = formatJson(entry);
1945
+ } else {
1946
+ const date = new Date(entry.timestamp).toISOString();
1947
+ line = `${date} [${entry.level.toUpperCase()}] ${entry.loggerName ? `[${entry.loggerName}] ` : ""}${entry.message}`;
1948
+ if (entry.context && Object.keys(entry.context).length > 0) {
1949
+ line += ` ${JSON.stringify(entry.context)}`;
1950
+ }
1951
+ }
1952
+ this.buffer.push(line + "\n");
1953
+ this.currentSize += line.length + 1;
1954
+ if (this.buffer.length >= 100 || this.currentSize >= 64 * 1024) {
1955
+ this.scheduleWrite();
1956
+ }
1957
+ }
1958
+ /**
1959
+ * Schedule a buffered write
1960
+ */
1961
+ scheduleWrite() {
1962
+ this.writePromise = this.writePromise.then(() => this.writeBuffer());
1963
+ }
1964
+ /**
1965
+ * Write buffer to file
1966
+ */
1967
+ async writeBuffer() {
1968
+ if (!this.fs || this.buffer.length === 0) {
1969
+ return;
1970
+ }
1971
+ await this.checkRotation();
1972
+ const data = this.buffer.join("");
1973
+ this.buffer = [];
1974
+ this.currentSize = 0;
1975
+ try {
1976
+ await this.fs.appendFile(this.config.path, data);
1977
+ } catch (error2) {
1978
+ console.error("[Statly Logger] Failed to write to file:", error2);
1979
+ }
1980
+ }
1981
+ /**
1982
+ * Check if rotation is needed
1983
+ */
1984
+ async checkRotation() {
1985
+ if (!this.fs) return;
1986
+ const { type } = this.config.rotation;
1987
+ let shouldRotate = false;
1988
+ if (type === "size") {
1989
+ try {
1990
+ const stats = await this.fs.stat(this.config.path);
1991
+ shouldRotate = stats.size >= this.maxSize;
1992
+ } catch {
1993
+ }
1994
+ } else if (type === "time") {
1995
+ const now = /* @__PURE__ */ new Date();
1996
+ shouldRotate = now.getTime() - this.lastRotation.getTime() >= this.rotationInterval;
1997
+ }
1998
+ if (shouldRotate) {
1999
+ await this.rotate();
2000
+ }
2001
+ }
2002
+ /**
2003
+ * Rotate the log file
2004
+ */
2005
+ async rotate() {
2006
+ if (!this.fs) return;
2007
+ try {
2008
+ const rotatedPath = `${this.config.path}.${formatDate(/* @__PURE__ */ new Date())}`;
2009
+ await this.fs.rename(this.config.path, rotatedPath);
2010
+ this.lastRotation = /* @__PURE__ */ new Date();
2011
+ await this.cleanupOldFiles();
2012
+ } catch (error2) {
2013
+ console.error("[Statly Logger] Failed to rotate file:", error2);
2014
+ }
2015
+ }
2016
+ /**
2017
+ * Clean up old rotated files
2018
+ */
2019
+ async cleanupOldFiles() {
2020
+ if (!this.fs) return;
2021
+ const { maxFiles, retentionDays } = this.config.rotation;
2022
+ try {
2023
+ const path = await import("path");
2024
+ const dir = path.dirname(this.config.path);
2025
+ const basename = path.basename(this.config.path);
2026
+ const files = await this.fs.readdir(dir);
2027
+ const rotatedFiles = files.filter((f) => f.startsWith(basename + ".")).map((f) => ({ name: f, path: path.join(dir, f) })).sort((a, b) => b.name.localeCompare(a.name));
2028
+ if (maxFiles) {
2029
+ for (const file of rotatedFiles.slice(maxFiles)) {
2030
+ await this.fs.unlink(file.path);
2031
+ }
2032
+ }
2033
+ if (retentionDays) {
2034
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
2035
+ for (const file of rotatedFiles) {
2036
+ const match = file.name.match(/\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$/);
2037
+ if (match) {
2038
+ const fileDate = new Date(match[1].replace("_", "T").replace(/-/g, ":"));
2039
+ if (fileDate.getTime() < cutoff) {
2040
+ await this.fs.unlink(file.path);
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+ } catch (error2) {
2046
+ console.error("[Statly Logger] Failed to cleanup old files:", error2);
2047
+ }
2048
+ }
2049
+ /**
2050
+ * Flush buffered writes
2051
+ */
2052
+ async flush() {
2053
+ this.scheduleWrite();
2054
+ await this.writePromise;
2055
+ }
2056
+ /**
2057
+ * Close the destination
2058
+ */
2059
+ async close() {
2060
+ await this.flush();
2061
+ }
2062
+ /**
2063
+ * Set minimum log level
2064
+ */
2065
+ setMinLevel(level) {
2066
+ this.minLevel = LOG_LEVELS[level];
2067
+ }
2068
+ /**
2069
+ * Enable or disable the destination
2070
+ */
2071
+ setEnabled(enabled) {
2072
+ this.config.enabled = enabled;
2073
+ }
2074
+ };
2075
+
2076
+ // src/logger/ai/index.ts
2077
+ var AIFeatures = class {
2078
+ constructor(dsn, config = {}) {
2079
+ this.dsn = dsn;
2080
+ this.config = {
2081
+ enabled: config.enabled ?? true,
2082
+ apiKey: config.apiKey || "",
2083
+ model: config.model || "claude-3-haiku-20240307",
2084
+ endpoint: config.endpoint || this.parseEndpoint(dsn)
2085
+ };
2086
+ }
2087
+ /**
2088
+ * Parse DSN to construct AI endpoint
2089
+ */
2090
+ parseEndpoint(dsn) {
2091
+ try {
2092
+ const url = new URL(dsn);
2093
+ return `${url.protocol}//${url.host}/api/v1/logs/ai`;
2094
+ } catch {
2095
+ return "https://statly.live/api/v1/logs/ai";
2096
+ }
2097
+ }
2098
+ /**
2099
+ * Explain an error using AI
2100
+ */
2101
+ async explainError(error2) {
2102
+ if (!this.config.enabled) {
2103
+ return {
2104
+ summary: "AI features are disabled",
2105
+ possibleCauses: []
2106
+ };
2107
+ }
2108
+ const errorData = this.normalizeError(error2);
2109
+ try {
2110
+ const response = await fetch(`${this.config.endpoint}/explain`, {
2111
+ method: "POST",
2112
+ headers: {
2113
+ "Content-Type": "application/json",
2114
+ "X-Statly-DSN": this.dsn,
2115
+ ...this.config.apiKey && { "X-AI-API-Key": this.config.apiKey }
2116
+ },
2117
+ body: JSON.stringify({
2118
+ error: errorData,
2119
+ model: this.config.model
2120
+ })
2121
+ });
2122
+ if (!response.ok) {
2123
+ throw new Error(`HTTP ${response.status}`);
2124
+ }
2125
+ return await response.json();
2126
+ } catch (err) {
2127
+ console.error("[Statly Logger AI] Failed to explain error:", err);
2128
+ return {
2129
+ summary: "Failed to get AI explanation",
2130
+ possibleCauses: []
2131
+ };
2132
+ }
2133
+ }
2134
+ /**
2135
+ * Suggest fixes for an error using AI
2136
+ */
2137
+ async suggestFix(error2, context) {
2138
+ if (!this.config.enabled) {
2139
+ return {
2140
+ summary: "AI features are disabled",
2141
+ suggestedFixes: []
2142
+ };
2143
+ }
2144
+ const errorData = this.normalizeError(error2);
2145
+ try {
2146
+ const response = await fetch(`${this.config.endpoint}/suggest-fix`, {
2147
+ method: "POST",
2148
+ headers: {
2149
+ "Content-Type": "application/json",
2150
+ "X-Statly-DSN": this.dsn,
2151
+ ...this.config.apiKey && { "X-AI-API-Key": this.config.apiKey }
2152
+ },
2153
+ body: JSON.stringify({
2154
+ error: errorData,
2155
+ context,
2156
+ model: this.config.model
2157
+ })
2158
+ });
2159
+ if (!response.ok) {
2160
+ throw new Error(`HTTP ${response.status}`);
2161
+ }
2162
+ return await response.json();
2163
+ } catch (err) {
2164
+ console.error("[Statly Logger AI] Failed to suggest fix:", err);
2165
+ return {
2166
+ summary: "Failed to get AI fix suggestion",
2167
+ suggestedFixes: []
2168
+ };
2169
+ }
2170
+ }
2171
+ /**
2172
+ * Analyze a batch of logs for patterns
2173
+ */
2174
+ async analyzePatterns(logs) {
2175
+ if (!this.config.enabled) {
2176
+ return {
2177
+ patterns: [],
2178
+ summary: "AI features are disabled",
2179
+ recommendations: []
2180
+ };
2181
+ }
2182
+ try {
2183
+ const response = await fetch(`${this.config.endpoint}/analyze-patterns`, {
2184
+ method: "POST",
2185
+ headers: {
2186
+ "Content-Type": "application/json",
2187
+ "X-Statly-DSN": this.dsn,
2188
+ ...this.config.apiKey && { "X-AI-API-Key": this.config.apiKey }
2189
+ },
2190
+ body: JSON.stringify({
2191
+ logs: logs.slice(0, 1e3),
2192
+ // Limit to 1000 logs
2193
+ model: this.config.model
2194
+ })
2195
+ });
2196
+ if (!response.ok) {
2197
+ throw new Error(`HTTP ${response.status}`);
2198
+ }
2199
+ return await response.json();
2200
+ } catch (err) {
2201
+ console.error("[Statly Logger AI] Failed to analyze patterns:", err);
2202
+ return {
2203
+ patterns: [],
2204
+ summary: "Failed to analyze patterns",
2205
+ recommendations: []
2206
+ };
2207
+ }
2208
+ }
2209
+ /**
2210
+ * Normalize error input to a standard format
2211
+ */
2212
+ normalizeError(error2) {
2213
+ if (typeof error2 === "string") {
2214
+ return { message: error2 };
2215
+ }
2216
+ if (error2 instanceof Error) {
2217
+ return {
2218
+ message: error2.message,
2219
+ stack: error2.stack,
2220
+ type: error2.name
2221
+ };
2222
+ }
2223
+ return {
2224
+ message: error2.message,
2225
+ type: error2.level,
2226
+ context: error2.context
2227
+ };
2228
+ }
2229
+ /**
2230
+ * Set API key for AI features
2231
+ */
2232
+ setApiKey(apiKey) {
2233
+ this.config.apiKey = apiKey;
2234
+ }
2235
+ /**
2236
+ * Enable or disable AI features
2237
+ */
2238
+ setEnabled(enabled) {
2239
+ this.config.enabled = enabled;
2240
+ }
2241
+ /**
2242
+ * Check if AI features are enabled
2243
+ */
2244
+ isEnabled() {
2245
+ return this.config.enabled;
2246
+ }
2247
+ };
2248
+
2249
+ // src/logger/logger.ts
2250
+ var SDK_NAME2 = "@statly/observe";
2251
+ var SDK_VERSION2 = "1.1.0";
2252
+ var Logger = class _Logger {
2253
+ constructor(config = {}) {
2254
+ this.destinations = [];
2255
+ this.ai = null;
2256
+ this.context = {};
2257
+ this.tags = {};
2258
+ this.name = config.loggerName || "default";
2259
+ this.config = config;
2260
+ this.minLevel = LOG_LEVELS[config.level || "debug"];
2261
+ this.enabledLevels = this.parseLevelSet(config.levels || "default");
2262
+ this.scrubber = new Scrubber(config.scrubbing);
2263
+ this.context = config.context || {};
2264
+ this.tags = config.tags || {};
2265
+ this.sessionId = this.generateId();
2266
+ this.initDestinations();
2267
+ if (config.dsn) {
2268
+ this.ai = new AIFeatures(config.dsn);
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Parse level set configuration
2273
+ */
2274
+ parseLevelSet(levels) {
2275
+ if (levels === "default") {
2276
+ return new Set(DEFAULT_LEVELS);
2277
+ }
2278
+ if (levels === "extended") {
2279
+ return new Set(EXTENDED_LEVELS);
2280
+ }
2281
+ return new Set(levels);
2282
+ }
2283
+ /**
2284
+ * Initialize destinations from config
2285
+ */
2286
+ initDestinations() {
2287
+ const { destinations } = this.config;
2288
+ if (!destinations || destinations.console?.enabled !== false) {
2289
+ this.destinations.push(new ConsoleDestination(destinations?.console));
2290
+ }
2291
+ if (destinations?.file?.enabled && destinations.file.path) {
2292
+ this.destinations.push(new FileDestination(destinations.file));
2293
+ }
2294
+ if (this.config.dsn && destinations?.observe?.enabled !== false) {
2295
+ this.destinations.push(new ObserveDestination(this.config.dsn, destinations?.observe));
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Generate a unique ID
2300
+ */
2301
+ generateId() {
2302
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
2303
+ return crypto.randomUUID();
2304
+ }
2305
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2306
+ const r = Math.random() * 16 | 0;
2307
+ const v = c === "x" ? r : r & 3 | 8;
2308
+ return v.toString(16);
2309
+ });
2310
+ }
2311
+ /**
2312
+ * Check if a level should be logged
2313
+ */
2314
+ shouldLog(level) {
2315
+ if (level === "audit") {
2316
+ return true;
2317
+ }
2318
+ if (LOG_LEVELS[level] < this.minLevel) {
2319
+ return false;
2320
+ }
2321
+ return this.enabledLevels.has(level);
2322
+ }
2323
+ /**
2324
+ * Get source location (if available)
2325
+ */
2326
+ getSource() {
2327
+ try {
2328
+ const err = new Error();
2329
+ const stack = err.stack?.split("\n");
2330
+ if (!stack || stack.length < 5) return void 0;
2331
+ for (let i = 3; i < stack.length; i++) {
2332
+ const frame = stack[i];
2333
+ if (!frame.includes("logger.ts") && !frame.includes("Logger.")) {
2334
+ const match = frame.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+)(?::\d+)?\)?/);
2335
+ if (match) {
2336
+ return {
2337
+ function: match[1] || void 0,
2338
+ file: match[2],
2339
+ line: parseInt(match[3], 10)
2340
+ };
2341
+ }
2342
+ }
2343
+ }
2344
+ } catch {
2345
+ }
2346
+ return void 0;
2347
+ }
2348
+ /**
2349
+ * Create a log entry
2350
+ */
2351
+ createEntry(level, message, context) {
2352
+ return {
2353
+ level,
2354
+ message: this.scrubber.scrubMessage(message),
2355
+ timestamp: Date.now(),
2356
+ loggerName: this.name,
2357
+ context: context ? this.scrubber.scrub({ ...this.context, ...context }) : this.scrubber.scrub(this.context),
2358
+ tags: this.tags,
2359
+ source: this.getSource(),
2360
+ traceId: this.traceId,
2361
+ spanId: this.spanId,
2362
+ sessionId: this.sessionId,
2363
+ environment: this.config.environment,
2364
+ release: this.config.release,
2365
+ sdkName: SDK_NAME2,
2366
+ sdkVersion: SDK_VERSION2
2367
+ };
2368
+ }
2369
+ /**
2370
+ * Write to all destinations
2371
+ */
2372
+ write(entry) {
2373
+ for (const dest of this.destinations) {
2374
+ try {
2375
+ dest.write(entry);
2376
+ } catch (error2) {
2377
+ console.error(`[Statly Logger] Failed to write to ${dest.name}:`, error2);
2378
+ }
2379
+ }
2380
+ }
2381
+ // ==================== Public Logging Methods ====================
2382
+ /**
2383
+ * Log a trace message
2384
+ */
2385
+ trace(message, context) {
2386
+ if (!this.shouldLog("trace")) return;
2387
+ this.write(this.createEntry("trace", message, context));
2388
+ }
2389
+ /**
2390
+ * Log a debug message
2391
+ */
2392
+ debug(message, context) {
2393
+ if (!this.shouldLog("debug")) return;
2394
+ this.write(this.createEntry("debug", message, context));
2395
+ }
2396
+ /**
2397
+ * Log an info message
2398
+ */
2399
+ info(message, context) {
2400
+ if (!this.shouldLog("info")) return;
2401
+ this.write(this.createEntry("info", message, context));
2402
+ }
2403
+ /**
2404
+ * Log a warning message
2405
+ */
2406
+ warn(message, context) {
2407
+ if (!this.shouldLog("warn")) return;
2408
+ this.write(this.createEntry("warn", message, context));
2409
+ }
2410
+ error(messageOrError, context) {
2411
+ if (!this.shouldLog("error")) return;
2412
+ if (messageOrError instanceof Error) {
2413
+ const entry = this.createEntry("error", messageOrError.message, {
2414
+ ...context,
2415
+ stack: messageOrError.stack,
2416
+ errorType: messageOrError.name
2417
+ });
2418
+ this.write(entry);
2419
+ } else {
2420
+ this.write(this.createEntry("error", messageOrError, context));
2421
+ }
2422
+ }
2423
+ fatal(messageOrError, context) {
2424
+ if (!this.shouldLog("fatal")) return;
2425
+ if (messageOrError instanceof Error) {
2426
+ const entry = this.createEntry("fatal", messageOrError.message, {
2427
+ ...context,
2428
+ stack: messageOrError.stack,
2429
+ errorType: messageOrError.name
2430
+ });
2431
+ this.write(entry);
2432
+ } else {
2433
+ this.write(this.createEntry("fatal", messageOrError, context));
2434
+ }
2435
+ }
2436
+ /**
2437
+ * Log an audit message (always logged, never sampled)
2438
+ */
2439
+ audit(message, context) {
2440
+ this.write(this.createEntry("audit", message, context));
2441
+ }
2442
+ /**
2443
+ * Log at a specific level
2444
+ */
2445
+ log(level, message, context) {
2446
+ if (!this.shouldLog(level)) return;
2447
+ this.write(this.createEntry(level, message, context));
2448
+ }
2449
+ // ==================== Context & Tags ====================
2450
+ /**
2451
+ * Set persistent context
2452
+ */
2453
+ setContext(context) {
2454
+ this.context = { ...this.context, ...context };
2455
+ }
2456
+ /**
2457
+ * Clear context
2458
+ */
2459
+ clearContext() {
2460
+ this.context = {};
2461
+ }
2462
+ /**
2463
+ * Set a tag
2464
+ */
2465
+ setTag(key, value) {
2466
+ this.tags[key] = value;
2467
+ }
2468
+ /**
2469
+ * Set multiple tags
2470
+ */
2471
+ setTags(tags) {
2472
+ this.tags = { ...this.tags, ...tags };
2473
+ }
2474
+ /**
2475
+ * Clear tags
2476
+ */
2477
+ clearTags() {
2478
+ this.tags = {};
2479
+ }
2480
+ // ==================== Tracing ====================
2481
+ /**
2482
+ * Set trace ID for distributed tracing
2483
+ */
2484
+ setTraceId(traceId) {
2485
+ this.traceId = traceId;
2486
+ }
2487
+ /**
2488
+ * Set span ID
2489
+ */
2490
+ setSpanId(spanId) {
2491
+ this.spanId = spanId;
2492
+ }
2493
+ /**
2494
+ * Clear tracing context
2495
+ */
2496
+ clearTracing() {
2497
+ this.traceId = void 0;
2498
+ this.spanId = void 0;
2499
+ }
2500
+ // ==================== Child Loggers ====================
2501
+ /**
2502
+ * Create a child logger with additional context
2503
+ */
2504
+ child(options = {}) {
2505
+ const childConfig = {
2506
+ ...this.config,
2507
+ loggerName: options.name || `${this.name}.child`,
2508
+ context: { ...this.context, ...options.context },
2509
+ tags: { ...this.tags, ...options.tags }
2510
+ };
2511
+ const child = new _Logger(childConfig);
2512
+ child.traceId = this.traceId;
2513
+ child.spanId = this.spanId;
2514
+ child.sessionId = this.sessionId;
2515
+ return child;
2516
+ }
2517
+ // ==================== AI Features ====================
2518
+ /**
2519
+ * Explain an error using AI
2520
+ */
2521
+ async explainError(error2) {
2522
+ if (!this.ai) {
2523
+ return {
2524
+ summary: "AI features not available (no DSN configured)",
2525
+ possibleCauses: []
2526
+ };
2527
+ }
2528
+ return this.ai.explainError(error2);
2529
+ }
2530
+ /**
2531
+ * Suggest fixes for an error using AI
2532
+ */
2533
+ async suggestFix(error2, context) {
2534
+ if (!this.ai) {
2535
+ return {
2536
+ summary: "AI features not available (no DSN configured)",
2537
+ suggestedFixes: []
2538
+ };
2539
+ }
2540
+ return this.ai.suggestFix(error2, context);
2541
+ }
2542
+ /**
2543
+ * Configure AI features
2544
+ */
2545
+ configureAI(config) {
2546
+ if (this.ai) {
2547
+ if (config.apiKey) this.ai.setApiKey(config.apiKey);
2548
+ if (config.enabled !== void 0) this.ai.setEnabled(config.enabled);
2549
+ }
2550
+ }
2551
+ // ==================== Destination Management ====================
2552
+ /**
2553
+ * Add a custom destination
2554
+ */
2555
+ addDestination(destination) {
2556
+ this.destinations.push(destination);
2557
+ }
2558
+ /**
2559
+ * Remove a destination by name
2560
+ */
2561
+ removeDestination(name) {
2562
+ this.destinations = this.destinations.filter((d) => d.name !== name);
2563
+ }
2564
+ /**
2565
+ * Get all destinations
2566
+ */
2567
+ getDestinations() {
2568
+ return [...this.destinations];
2569
+ }
2570
+ // ==================== Level Configuration ====================
2571
+ /**
2572
+ * Set minimum log level
2573
+ */
2574
+ setLevel(level) {
2575
+ this.minLevel = LOG_LEVELS[level];
2576
+ }
2577
+ /**
2578
+ * Get current minimum level
2579
+ */
2580
+ getLevel() {
2581
+ const entries = Object.entries(LOG_LEVELS);
2582
+ const entry = entries.find(([, value]) => value === this.minLevel);
2583
+ return entry ? entry[0] : "debug";
2584
+ }
2585
+ /**
2586
+ * Check if a level is enabled
2587
+ */
2588
+ isLevelEnabled(level) {
2589
+ return this.shouldLog(level);
2590
+ }
2591
+ // ==================== Lifecycle ====================
2592
+ /**
2593
+ * Flush all destinations
2594
+ */
2595
+ async flush() {
2596
+ await Promise.all(
2597
+ this.destinations.filter((d) => d.flush).map((d) => d.flush())
2598
+ );
2599
+ }
2600
+ /**
2601
+ * Close the logger and all destinations
2602
+ */
2603
+ async close() {
2604
+ await Promise.all(
2605
+ this.destinations.filter((d) => d.close).map((d) => d.close())
2606
+ );
2607
+ }
2608
+ /**
2609
+ * Get logger name
2610
+ */
2611
+ getName() {
2612
+ return this.name;
2613
+ }
2614
+ /**
2615
+ * Get session ID
2616
+ */
2617
+ getSessionId() {
2618
+ return this.sessionId;
2619
+ }
2620
+ };
2621
+
2622
+ // src/logger/index.ts
2623
+ var defaultLogger = null;
2624
+ function getDefaultLogger() {
2625
+ if (!defaultLogger) {
2626
+ defaultLogger = new Logger();
2627
+ }
2628
+ return defaultLogger;
2629
+ }
2630
+ function setDefaultLogger(logger) {
2631
+ defaultLogger = logger;
2632
+ }
2633
+ function trace2(message, context) {
2634
+ getDefaultLogger().trace(message, context);
2635
+ }
2636
+ function debug(message, context) {
2637
+ getDefaultLogger().debug(message, context);
2638
+ }
2639
+ function info(message, context) {
2640
+ getDefaultLogger().info(message, context);
2641
+ }
2642
+ function warn(message, context) {
2643
+ getDefaultLogger().warn(message, context);
2644
+ }
2645
+ function error(messageOrError, context) {
2646
+ if (messageOrError instanceof Error) {
2647
+ getDefaultLogger().error(messageOrError, context);
2648
+ } else {
2649
+ getDefaultLogger().error(messageOrError, context);
2650
+ }
2651
+ }
2652
+ function fatal(messageOrError, context) {
2653
+ if (messageOrError instanceof Error) {
2654
+ getDefaultLogger().fatal(messageOrError, context);
2655
+ } else {
2656
+ getDefaultLogger().fatal(messageOrError, context);
2657
+ }
2658
+ }
2659
+ function audit(message, context) {
2660
+ getDefaultLogger().audit(message, context);
2661
+ }
2662
+
2663
+ // src/index.ts
2664
+ var client = null;
2665
+ function loadDsnFromEnv() {
2666
+ if (typeof process !== "undefined" && process.env) {
2667
+ return process.env.STATLY_DSN || process.env.NEXT_PUBLIC_STATLY_DSN || process.env.STATLY_OBSERVE_DSN;
2668
+ }
2669
+ return void 0;
2670
+ }
2671
+ function loadEnvironmentFromEnv() {
2672
+ if (typeof process !== "undefined" && process.env) {
2673
+ return process.env.STATLY_ENVIRONMENT || process.env.NODE_ENV;
2674
+ }
2675
+ return void 0;
2676
+ }
2677
+ function init(options) {
2678
+ if (client) {
2679
+ console.warn("[Statly] SDK already initialized. Call close() first to reinitialize.");
2680
+ return;
2681
+ }
2682
+ const dsn = options?.dsn || loadDsnFromEnv();
2683
+ if (!dsn) {
2684
+ console.error("[Statly] No DSN provided. Set STATLY_DSN in your environment or pass dsn to init().");
2685
+ console.error("[Statly] Get your DSN at https://statly.live/dashboard/observe/setup");
2686
+ return;
2687
+ }
2688
+ const environment = options?.environment || loadEnvironmentFromEnv();
2689
+ const finalOptions = {
2690
+ ...options,
2691
+ dsn,
2692
+ environment
2693
+ };
2694
+ client = new StatlyClient(finalOptions);
2695
+ client.init();
2696
+ }
2697
+ function captureException(error2, context) {
2698
+ if (!client) {
2699
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2700
+ return "";
2701
+ }
2702
+ return client.captureException(error2, context);
2703
+ }
2704
+ function captureMessage(message, level = "info") {
2705
+ if (!client) {
2706
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2707
+ return "";
2708
+ }
2709
+ return client.captureMessage(message, level);
2710
+ }
2711
+ function setUser(user) {
2712
+ if (!client) {
2713
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2714
+ return;
2715
+ }
2716
+ client.setUser(user);
2717
+ }
2718
+ function setTag(key, value) {
2719
+ if (!client) {
2720
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2721
+ return;
2722
+ }
2723
+ client.setTag(key, value);
2724
+ }
2725
+ function setTags(tags) {
2726
+ if (!client) {
2727
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2728
+ return;
2729
+ }
2730
+ client.setTags(tags);
2731
+ }
2732
+ function addBreadcrumb(breadcrumb) {
2733
+ if (!client) {
2734
+ console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
2735
+ return;
2736
+ }
2737
+ client.addBreadcrumb(breadcrumb);
2738
+ }
2739
+ async function flush() {
2740
+ if (!client) {
2741
+ return;
2742
+ }
2743
+ await client.flush();
2744
+ }
2745
+ async function close() {
2746
+ if (!client) {
2747
+ return;
2748
+ }
2749
+ await client.close();
2750
+ client = null;
2751
+ }
2752
+ function getClient() {
2753
+ return client;
2754
+ }
2755
+ async function trace3(name, operation, tags) {
2756
+ if (!client) {
2757
+ return operation(null);
2758
+ }
2759
+ return client.trace(name, operation, tags);
2760
+ }
2761
+ function startSpan(name, tags) {
2762
+ if (!client) return null;
2763
+ return client.startSpan(name, tags);
2764
+ }
2765
+ function captureSpan(span) {
2766
+ if (!client) return "";
2767
+ return client.captureSpan(span);
2768
+ }
2769
+ var Statly = {
2770
+ init,
2771
+ captureException,
2772
+ captureMessage,
2773
+ setUser,
2774
+ setTag,
2775
+ setTags,
2776
+ addBreadcrumb,
2777
+ flush,
2778
+ close,
2779
+ getClient,
2780
+ trace: trace3,
2781
+ startSpan,
2782
+ captureSpan
1136
2783
  };
1137
2784
  // Annotate the CommonJS export names for ESM import in node:
1138
2785
  0 && (module.exports = {
2786
+ AIFeatures,
2787
+ ConsoleDestination,
2788
+ DEFAULT_LEVELS,
2789
+ EXTENDED_LEVELS,
2790
+ FileDestination,
2791
+ LOG_LEVELS,
2792
+ Logger,
2793
+ ObserveDestination,
2794
+ REDACTED,
2795
+ SCRUB_PATTERNS,
2796
+ SENSITIVE_KEYS,
2797
+ Scrubber,
1139
2798
  Statly,
1140
2799
  StatlyClient,
1141
2800
  addBreadcrumb,
1142
2801
  captureException,
1143
2802
  captureMessage,
1144
2803
  captureNextJsError,
2804
+ captureSpan,
1145
2805
  close,
1146
2806
  createRequestCapture,
1147
2807
  expressErrorHandler,
1148
2808
  flush,
2809
+ formatJson,
2810
+ formatJsonPretty,
2811
+ formatPretty,
1149
2812
  getClient,
2813
+ getConsoleMethod,
2814
+ getDefaultLogger,
1150
2815
  init,
2816
+ isSensitiveKey,
2817
+ logAudit,
2818
+ logDebug,
2819
+ logError,
2820
+ logFatal,
2821
+ logInfo,
2822
+ logTrace,
2823
+ logWarn,
1151
2824
  requestHandler,
2825
+ setDefaultLogger,
1152
2826
  setTag,
1153
2827
  setTags,
1154
2828
  setUser,
2829
+ startSpan,
1155
2830
  statlyFastifyPlugin,
1156
2831
  statlyPlugin,
2832
+ trace,
1157
2833
  withStatly,
1158
2834
  withStatlyGetServerSideProps,
1159
2835
  withStatlyGetStaticProps,