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