@statly/observe 1.1.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,7 +1,9 @@
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;
6
8
  var __esm = (fn, res) => function __init() {
7
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -18,6 +20,14 @@ var __copyProps = (to, from, except, desc) => {
18
20
  }
19
21
  return to;
20
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
+ ));
21
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
32
 
23
33
  // src/span.ts
@@ -105,14 +115,14 @@ async function trace(name, operation, tags) {
105
115
  try {
106
116
  const result = await operation(span);
107
117
  return result;
108
- } catch (error) {
118
+ } catch (error2) {
109
119
  span.setStatus("error" /* ERROR */);
110
120
  span.setTag("error", "true");
111
- if (error instanceof Error) {
112
- span.setTag("exception.type", error.name);
113
- span.setTag("exception.message", error.message);
121
+ if (error2 instanceof Error) {
122
+ span.setTag("exception.type", error2.name);
123
+ span.setTag("exception.message", error2.message);
114
124
  }
115
- throw error;
125
+ throw error2;
116
126
  } finally {
117
127
  provider.finishSpan(span);
118
128
  }
@@ -172,6 +182,18 @@ var init_telemetry = __esm({
172
182
  // src/index.ts
173
183
  var index_exports = {};
174
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,
175
197
  Statly: () => Statly,
176
198
  StatlyClient: () => StatlyClient,
177
199
  addBreadcrumb: () => addBreadcrumb,
@@ -183,16 +205,30 @@ __export(index_exports, {
183
205
  createRequestCapture: () => createRequestCapture,
184
206
  expressErrorHandler: () => expressErrorHandler,
185
207
  flush: () => flush,
208
+ formatJson: () => formatJson,
209
+ formatJsonPretty: () => formatJsonPretty,
210
+ formatPretty: () => formatPretty,
186
211
  getClient: () => getClient,
212
+ getConsoleMethod: () => getConsoleMethod,
213
+ getDefaultLogger: () => getDefaultLogger,
187
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,
188
223
  requestHandler: () => requestHandler,
224
+ setDefaultLogger: () => setDefaultLogger,
189
225
  setTag: () => setTag,
190
226
  setTags: () => setTags,
191
227
  setUser: () => setUser,
192
228
  startSpan: () => startSpan,
193
229
  statlyFastifyPlugin: () => statlyFastifyPlugin,
194
230
  statlyPlugin: () => statlyPlugin,
195
- trace: () => trace2,
231
+ trace: () => trace3,
196
232
  withStatly: () => withStatly,
197
233
  withStatlyGetServerSideProps: () => withStatlyGetServerSideProps,
198
234
  withStatlyGetStaticProps: () => withStatlyGetStaticProps,
@@ -261,10 +297,10 @@ var Transport = class {
261
297
  this.queue = [];
262
298
  try {
263
299
  await this.sendBatch(events);
264
- } catch (error) {
300
+ } catch (error2) {
265
301
  this.queue = [...events, ...this.queue].slice(0, this.maxQueueSize);
266
302
  if (this.debug) {
267
- console.error("[Statly] Failed to send events:", error);
303
+ console.error("[Statly] Failed to send events:", error2);
268
304
  }
269
305
  } finally {
270
306
  this.isSending = false;
@@ -304,13 +340,13 @@ var Transport = class {
304
340
  console.log(`[Statly] Sent ${events.length} event(s)`);
305
341
  }
306
342
  return { success: true, status: response.status };
307
- } catch (error) {
343
+ } catch (error2) {
308
344
  if (this.debug) {
309
- console.error("[Statly] Network error:", error);
345
+ console.error("[Statly] Network error:", error2);
310
346
  }
311
347
  return {
312
348
  success: false,
313
- error: error instanceof Error ? error.message : "Network error"
349
+ error: error2 instanceof Error ? error2.message : "Network error"
314
350
  };
315
351
  }
316
352
  }
@@ -377,16 +413,16 @@ var GlobalHandlers = class {
377
413
  if (!this.errorCallback) {
378
414
  return;
379
415
  }
380
- let error;
416
+ let error2;
381
417
  if (event.reason instanceof Error) {
382
- error = event.reason;
418
+ error2 = event.reason;
383
419
  } else if (typeof event.reason === "string") {
384
- error = new Error(event.reason);
420
+ error2 = new Error(event.reason);
385
421
  } else {
386
- error = new Error("Unhandled Promise Rejection");
387
- error.reason = event.reason;
422
+ error2 = new Error("Unhandled Promise Rejection");
423
+ error2.reason = event.reason;
388
424
  }
389
- this.errorCallback(error, {
425
+ this.errorCallback(error2, {
390
426
  mechanism: { type: "onunhandledrejection", handled: false }
391
427
  });
392
428
  };
@@ -429,13 +465,13 @@ var GlobalHandlers = class {
429
465
  }
430
466
  installOnError() {
431
467
  this.originalOnError = window.onerror;
432
- window.onerror = (message, source, lineno, colno, error) => {
468
+ window.onerror = (message, source, lineno, colno, error2) => {
433
469
  if (this.originalOnError) {
434
- this.originalOnError.call(window, message, source, lineno, colno, error);
470
+ this.originalOnError.call(window, message, source, lineno, colno, error2);
435
471
  }
436
472
  if (this.errorCallback) {
437
- const errorObj = error || new Error(String(message));
438
- if (!error && source) {
473
+ const errorObj = error2 || new Error(String(message));
474
+ if (!error2 && source) {
439
475
  errorObj.filename = source;
440
476
  errorObj.lineno = lineno;
441
477
  errorObj.colno = colno;
@@ -600,8 +636,8 @@ var StatlyClient = class {
600
636
  }
601
637
  this.initialized = true;
602
638
  if (this.options.autoCapture) {
603
- this.globalHandlers.install((error, context) => {
604
- this.captureError(error, context);
639
+ this.globalHandlers.install((error2, context) => {
640
+ this.captureError(error2, context);
605
641
  });
606
642
  }
607
643
  if (this.options.captureConsole) {
@@ -624,15 +660,15 @@ var StatlyClient = class {
624
660
  /**
625
661
  * Capture an exception/error
626
662
  */
627
- captureException(error, context) {
663
+ captureException(error2, context) {
628
664
  let errorObj;
629
- if (error instanceof Error) {
630
- errorObj = error;
631
- } else if (typeof error === "string") {
632
- errorObj = new Error(error);
665
+ if (error2 instanceof Error) {
666
+ errorObj = error2;
667
+ } else if (typeof error2 === "string") {
668
+ errorObj = new Error(error2);
633
669
  } else {
634
670
  errorObj = new Error("Unknown error");
635
- errorObj.originalError = error;
671
+ errorObj.originalError = error2;
636
672
  }
637
673
  return this.captureError(errorObj, context);
638
674
  }
@@ -673,18 +709,18 @@ var StatlyClient = class {
673
709
  /**
674
710
  * Internal method to capture an error
675
711
  */
676
- captureError(error, context) {
712
+ captureError(error2, context) {
677
713
  if (Math.random() > this.options.sampleRate) {
678
714
  return "";
679
715
  }
680
716
  const event = this.buildEvent({
681
- message: error.message,
717
+ message: error2.message,
682
718
  level: "error",
683
- stack: error.stack,
719
+ stack: error2.stack,
684
720
  exception: {
685
- type: error.name,
686
- value: error.message,
687
- stacktrace: this.parseStackTrace(error.stack)
721
+ type: error2.name,
722
+ value: error2.message,
723
+ stacktrace: this.parseStackTrace(error2.stack)
688
724
  },
689
725
  extra: context
690
726
  });
@@ -917,8 +953,8 @@ function requestHandler() {
917
953
  }
918
954
  function expressErrorHandler(options = {}) {
919
955
  return (err, req, res, next) => {
920
- const error = err instanceof Error ? err : new Error(String(err));
921
- if (options.shouldHandleError && !options.shouldHandleError(error)) {
956
+ const error2 = err instanceof Error ? err : new Error(String(err));
957
+ if (options.shouldHandleError && !options.shouldHandleError(error2)) {
922
958
  return next(err);
923
959
  }
924
960
  const context = {
@@ -942,7 +978,7 @@ function expressErrorHandler(options = {}) {
942
978
  if (req.statlyContext?.transactionName) {
943
979
  Statly.setTag("transaction", req.statlyContext.transactionName);
944
980
  }
945
- Statly.captureException(error, context);
981
+ Statly.captureException(error2, context);
946
982
  next(err);
947
983
  };
948
984
  }
@@ -995,7 +1031,7 @@ function withStatlyPagesApi(handler) {
995
1031
  try {
996
1032
  const result = await handler(req, res);
997
1033
  return result;
998
- } catch (error) {
1034
+ } catch (error2) {
999
1035
  const context = {
1000
1036
  request: {
1001
1037
  method: req.method,
@@ -1004,8 +1040,8 @@ function withStatlyPagesApi(handler) {
1004
1040
  query: req.query
1005
1041
  }
1006
1042
  };
1007
- Statly.captureException(error, context);
1008
- throw error;
1043
+ Statly.captureException(error2, context);
1044
+ throw error2;
1009
1045
  }
1010
1046
  });
1011
1047
  };
@@ -1031,7 +1067,7 @@ function withStatly(handler) {
1031
1067
  span.setTag("http.status_code", result.status.toString());
1032
1068
  }
1033
1069
  return result;
1034
- } catch (error) {
1070
+ } catch (error2) {
1035
1071
  const headers = {};
1036
1072
  request.headers.forEach((value, key) => {
1037
1073
  headers[key] = value;
@@ -1050,17 +1086,17 @@ function withStatly(handler) {
1050
1086
  } catch {
1051
1087
  }
1052
1088
  }
1053
- Statly.captureException(error, errorContext);
1054
- throw error;
1089
+ Statly.captureException(error2, errorContext);
1090
+ throw error2;
1055
1091
  }
1056
1092
  });
1057
1093
  };
1058
1094
  return wrappedHandler;
1059
1095
  }
1060
- function captureNextJsError(error, context) {
1061
- return Statly.captureException(error, {
1096
+ function captureNextJsError(error2, context) {
1097
+ return Statly.captureException(error2, {
1062
1098
  ...context,
1063
- digest: error.digest,
1099
+ digest: error2.digest,
1064
1100
  source: "nextjs-error-boundary"
1065
1101
  });
1066
1102
  }
@@ -1068,12 +1104,12 @@ function withStatlyGetServerSideProps(handler) {
1068
1104
  return async (context) => {
1069
1105
  try {
1070
1106
  return await handler(context);
1071
- } catch (error) {
1072
- Statly.captureException(error, {
1107
+ } catch (error2) {
1108
+ Statly.captureException(error2, {
1073
1109
  source: "getServerSideProps",
1074
1110
  url: context.req?.url || context.resolvedUrl
1075
1111
  });
1076
- throw error;
1112
+ throw error2;
1077
1113
  }
1078
1114
  };
1079
1115
  }
@@ -1081,12 +1117,12 @@ function withStatlyGetStaticProps(handler) {
1081
1117
  return async (context) => {
1082
1118
  try {
1083
1119
  return await handler(context);
1084
- } catch (error) {
1085
- Statly.captureException(error, {
1120
+ } catch (error2) {
1121
+ Statly.captureException(error2, {
1086
1122
  source: "getStaticProps",
1087
1123
  params: context.params
1088
1124
  });
1089
- throw error;
1125
+ throw error2;
1090
1126
  }
1091
1127
  };
1092
1128
  }
@@ -1102,12 +1138,12 @@ function withStatlyServerAction(action, actionName) {
1102
1138
  });
1103
1139
  try {
1104
1140
  return await action(...args);
1105
- } catch (error) {
1106
- Statly.captureException(error, {
1141
+ } catch (error2) {
1142
+ Statly.captureException(error2, {
1107
1143
  source: "server-action",
1108
1144
  actionName
1109
1145
  });
1110
- throw error;
1146
+ throw error2;
1111
1147
  }
1112
1148
  });
1113
1149
  };
@@ -1162,16 +1198,16 @@ function statlyFastifyPlugin(fastify, options, done) {
1162
1198
  });
1163
1199
  hookDone();
1164
1200
  });
1165
- fastify.setErrorHandler((error, request, reply) => {
1166
- const statusCode = error.statusCode || 500;
1201
+ fastify.setErrorHandler((error2, request, reply) => {
1202
+ const statusCode = error2.statusCode || 500;
1167
1203
  if (skipStatusCodes.includes(statusCode)) {
1168
- throw error;
1204
+ throw error2;
1169
1205
  }
1170
- if (!captureValidationErrors && error.validation) {
1171
- throw error;
1206
+ if (!captureValidationErrors && error2.validation) {
1207
+ throw error2;
1172
1208
  }
1173
- if (shouldCapture && !shouldCapture(error)) {
1174
- throw error;
1209
+ if (shouldCapture && !shouldCapture(error2)) {
1210
+ throw error2;
1175
1211
  }
1176
1212
  const context = {
1177
1213
  request: {
@@ -1184,27 +1220,27 @@ function statlyFastifyPlugin(fastify, options, done) {
1184
1220
  params: request.params
1185
1221
  },
1186
1222
  error: {
1187
- statusCode: error.statusCode,
1188
- code: error.code
1223
+ statusCode: error2.statusCode,
1224
+ code: error2.code
1189
1225
  }
1190
1226
  };
1191
1227
  if (request.ip) {
1192
1228
  context.ip = request.ip;
1193
1229
  }
1194
- if (error.validation) {
1195
- context.validation = error.validation;
1230
+ if (error2.validation) {
1231
+ context.validation = error2.validation;
1196
1232
  }
1197
1233
  Statly.setTag("http.method", request.method);
1198
1234
  Statly.setTag("http.url", request.routerPath || request.url);
1199
1235
  Statly.setTag("http.status_code", String(statusCode));
1200
- Statly.captureException(error, context);
1201
- throw error;
1236
+ Statly.captureException(error2, context);
1237
+ throw error2;
1202
1238
  });
1203
1239
  done();
1204
1240
  }
1205
1241
  var statlyPlugin = statlyFastifyPlugin;
1206
1242
  function createRequestCapture(request) {
1207
- return (error, additionalContext) => {
1243
+ return (error2, additionalContext) => {
1208
1244
  const context = {
1209
1245
  request: {
1210
1246
  id: request.id,
@@ -1214,7 +1250,7 @@ function createRequestCapture(request) {
1214
1250
  },
1215
1251
  ...additionalContext
1216
1252
  };
1217
- return Statly.captureException(error, context);
1253
+ return Statly.captureException(error2, context);
1218
1254
  };
1219
1255
  }
1220
1256
  function sanitizeHeaders3(headers) {
@@ -1230,6 +1266,1400 @@ function sanitizeHeaders3(headers) {
1230
1266
  return sanitized;
1231
1267
  }
1232
1268
 
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"
1357
+ }
1358
+ };
1359
+ var REDACTED = "[REDACTED]";
1360
+ function isSensitiveKey(key) {
1361
+ const lowerKey = key.toLowerCase();
1362
+ return SENSITIVE_KEYS.has(lowerKey);
1363
+ }
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
+ }
1389
+ }
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, "");
1398
+ }
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;
1414
+ }
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;
1444
+ }
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;
1459
+ }
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;
1469
+ }
1470
+ /**
1471
+ * Add a custom pattern at runtime
1472
+ */
1473
+ addPattern(pattern) {
1474
+ this.customPatterns.push(pattern);
1475
+ }
1476
+ /**
1477
+ * Add a key to the allowlist
1478
+ */
1479
+ addToAllowlist(key) {
1480
+ this.allowlist.add(key.toLowerCase());
1481
+ }
1482
+ /**
1483
+ * Check if scrubbing is enabled
1484
+ */
1485
+ isEnabled() {
1486
+ return this.enabled;
1487
+ }
1488
+ /**
1489
+ * Enable or disable scrubbing
1490
+ */
1491
+ setEnabled(enabled) {
1492
+ this.enabled = enabled;
1493
+ }
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);
1547
+ }
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;
1570
+ }
1571
+ function formatJson(entry) {
1572
+ return JSON.stringify(entry);
1573
+ }
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
+
1233
2663
  // src/index.ts
1234
2664
  var client = null;
1235
2665
  function loadDsnFromEnv() {
@@ -1264,12 +2694,12 @@ function init(options) {
1264
2694
  client = new StatlyClient(finalOptions);
1265
2695
  client.init();
1266
2696
  }
1267
- function captureException(error, context) {
2697
+ function captureException(error2, context) {
1268
2698
  if (!client) {
1269
2699
  console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
1270
2700
  return "";
1271
2701
  }
1272
- return client.captureException(error, context);
2702
+ return client.captureException(error2, context);
1273
2703
  }
1274
2704
  function captureMessage(message, level = "info") {
1275
2705
  if (!client) {
@@ -1322,7 +2752,7 @@ async function close() {
1322
2752
  function getClient() {
1323
2753
  return client;
1324
2754
  }
1325
- async function trace2(name, operation, tags) {
2755
+ async function trace3(name, operation, tags) {
1326
2756
  if (!client) {
1327
2757
  return operation(null);
1328
2758
  }
@@ -1347,12 +2777,24 @@ var Statly = {
1347
2777
  flush,
1348
2778
  close,
1349
2779
  getClient,
1350
- trace: trace2,
2780
+ trace: trace3,
1351
2781
  startSpan,
1352
2782
  captureSpan
1353
2783
  };
1354
2784
  // Annotate the CommonJS export names for ESM import in node:
1355
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,
1356
2798
  Statly,
1357
2799
  StatlyClient,
1358
2800
  addBreadcrumb,
@@ -1364,9 +2806,23 @@ var Statly = {
1364
2806
  createRequestCapture,
1365
2807
  expressErrorHandler,
1366
2808
  flush,
2809
+ formatJson,
2810
+ formatJsonPretty,
2811
+ formatPretty,
1367
2812
  getClient,
2813
+ getConsoleMethod,
2814
+ getDefaultLogger,
1368
2815
  init,
2816
+ isSensitiveKey,
2817
+ logAudit,
2818
+ logDebug,
2819
+ logError,
2820
+ logFatal,
2821
+ logInfo,
2822
+ logTrace,
2823
+ logWarn,
1369
2824
  requestHandler,
2825
+ setDefaultLogger,
1370
2826
  setTag,
1371
2827
  setTags,
1372
2828
  setUser,