@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/README.md +104 -0
- package/dist/chunk-7AITSJLP.mjs +1422 -0
- package/dist/{chunk-HYFH22G6.mjs → chunk-SJ7C46AP.mjs} +70 -70
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1531 -75
- package/dist/index.mjs +57 -3
- package/dist/integrations/express.js +70 -41
- package/dist/integrations/express.mjs +2 -1
- package/dist/integrations/fastify.js +82 -53
- package/dist/integrations/fastify.mjs +2 -1
- package/dist/integrations/nextjs.js +85 -56
- package/dist/integrations/nextjs.mjs +2 -1
- package/dist/logger/index.d.mts +671 -0
- package/dist/logger/index.d.ts +671 -0
- package/dist/logger/index.js +1483 -0
- package/dist/logger/index.mjs +56 -0
- package/package.json +7 -2
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 (
|
|
118
|
+
} catch (error2) {
|
|
109
119
|
span.setStatus("error" /* ERROR */);
|
|
110
120
|
span.setTag("error", "true");
|
|
111
|
-
if (
|
|
112
|
-
span.setTag("exception.type",
|
|
113
|
-
span.setTag("exception.message",
|
|
121
|
+
if (error2 instanceof Error) {
|
|
122
|
+
span.setTag("exception.type", error2.name);
|
|
123
|
+
span.setTag("exception.message", error2.message);
|
|
114
124
|
}
|
|
115
|
-
throw
|
|
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: () =>
|
|
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 (
|
|
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:",
|
|
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 (
|
|
343
|
+
} catch (error2) {
|
|
308
344
|
if (this.debug) {
|
|
309
|
-
console.error("[Statly] Network error:",
|
|
345
|
+
console.error("[Statly] Network error:", error2);
|
|
310
346
|
}
|
|
311
347
|
return {
|
|
312
348
|
success: false,
|
|
313
|
-
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
|
|
416
|
+
let error2;
|
|
381
417
|
if (event.reason instanceof Error) {
|
|
382
|
-
|
|
418
|
+
error2 = event.reason;
|
|
383
419
|
} else if (typeof event.reason === "string") {
|
|
384
|
-
|
|
420
|
+
error2 = new Error(event.reason);
|
|
385
421
|
} else {
|
|
386
|
-
|
|
387
|
-
|
|
422
|
+
error2 = new Error("Unhandled Promise Rejection");
|
|
423
|
+
error2.reason = event.reason;
|
|
388
424
|
}
|
|
389
|
-
this.errorCallback(
|
|
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,
|
|
468
|
+
window.onerror = (message, source, lineno, colno, error2) => {
|
|
433
469
|
if (this.originalOnError) {
|
|
434
|
-
this.originalOnError.call(window, message, source, lineno, colno,
|
|
470
|
+
this.originalOnError.call(window, message, source, lineno, colno, error2);
|
|
435
471
|
}
|
|
436
472
|
if (this.errorCallback) {
|
|
437
|
-
const errorObj =
|
|
438
|
-
if (!
|
|
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((
|
|
604
|
-
this.captureError(
|
|
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(
|
|
663
|
+
captureException(error2, context) {
|
|
628
664
|
let errorObj;
|
|
629
|
-
if (
|
|
630
|
-
errorObj =
|
|
631
|
-
} else if (typeof
|
|
632
|
-
errorObj = new 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 =
|
|
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(
|
|
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:
|
|
717
|
+
message: error2.message,
|
|
682
718
|
level: "error",
|
|
683
|
-
stack:
|
|
719
|
+
stack: error2.stack,
|
|
684
720
|
exception: {
|
|
685
|
-
type:
|
|
686
|
-
value:
|
|
687
|
-
stacktrace: this.parseStackTrace(
|
|
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
|
|
921
|
-
if (options.shouldHandleError && !options.shouldHandleError(
|
|
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(
|
|
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 (
|
|
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(
|
|
1008
|
-
throw
|
|
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 (
|
|
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(
|
|
1054
|
-
throw
|
|
1089
|
+
Statly.captureException(error2, errorContext);
|
|
1090
|
+
throw error2;
|
|
1055
1091
|
}
|
|
1056
1092
|
});
|
|
1057
1093
|
};
|
|
1058
1094
|
return wrappedHandler;
|
|
1059
1095
|
}
|
|
1060
|
-
function captureNextJsError(
|
|
1061
|
-
return Statly.captureException(
|
|
1096
|
+
function captureNextJsError(error2, context) {
|
|
1097
|
+
return Statly.captureException(error2, {
|
|
1062
1098
|
...context,
|
|
1063
|
-
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 (
|
|
1072
|
-
Statly.captureException(
|
|
1107
|
+
} catch (error2) {
|
|
1108
|
+
Statly.captureException(error2, {
|
|
1073
1109
|
source: "getServerSideProps",
|
|
1074
1110
|
url: context.req?.url || context.resolvedUrl
|
|
1075
1111
|
});
|
|
1076
|
-
throw
|
|
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 (
|
|
1085
|
-
Statly.captureException(
|
|
1120
|
+
} catch (error2) {
|
|
1121
|
+
Statly.captureException(error2, {
|
|
1086
1122
|
source: "getStaticProps",
|
|
1087
1123
|
params: context.params
|
|
1088
1124
|
});
|
|
1089
|
-
throw
|
|
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 (
|
|
1106
|
-
Statly.captureException(
|
|
1141
|
+
} catch (error2) {
|
|
1142
|
+
Statly.captureException(error2, {
|
|
1107
1143
|
source: "server-action",
|
|
1108
1144
|
actionName
|
|
1109
1145
|
});
|
|
1110
|
-
throw
|
|
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((
|
|
1166
|
-
const statusCode =
|
|
1201
|
+
fastify.setErrorHandler((error2, request, reply) => {
|
|
1202
|
+
const statusCode = error2.statusCode || 500;
|
|
1167
1203
|
if (skipStatusCodes.includes(statusCode)) {
|
|
1168
|
-
throw
|
|
1204
|
+
throw error2;
|
|
1169
1205
|
}
|
|
1170
|
-
if (!captureValidationErrors &&
|
|
1171
|
-
throw
|
|
1206
|
+
if (!captureValidationErrors && error2.validation) {
|
|
1207
|
+
throw error2;
|
|
1172
1208
|
}
|
|
1173
|
-
if (shouldCapture && !shouldCapture(
|
|
1174
|
-
throw
|
|
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:
|
|
1188
|
-
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 (
|
|
1195
|
-
context.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(
|
|
1201
|
-
throw
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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,
|