@statly/observe 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-SJ7C46AP.mjs → chunk-RLSLJL6N.js} +7 -8
- package/dist/{chunk-7AITSJLP.mjs → chunk-RUEXBTHM.js} +2 -2
- package/dist/{cli.mjs → cli.cjs} +26 -3
- package/dist/cli.js +3 -26
- package/dist/index.cjs +2837 -0
- package/dist/{index.d.mts → index.d.cts} +4 -4
- package/dist/index.js +62 -2789
- package/dist/integrations/express.cjs +1108 -0
- package/dist/integrations/express.js +8 -1107
- package/dist/integrations/fastify.cjs +1117 -0
- package/dist/integrations/fastify.js +9 -1115
- package/dist/integrations/nextjs.cjs +1167 -0
- package/dist/integrations/nextjs.js +12 -1162
- package/dist/logger/index.cjs +1483 -0
- package/dist/logger/index.js +30 -1457
- package/dist/{telemetry-CXHOTW3Y.mjs → telemetry-JOMOMZ25.js} +1 -1
- package/package.json +3 -2
- package/dist/index.mjs +0 -111
- package/dist/integrations/express.mjs +0 -10
- package/dist/integrations/fastify.mjs +0 -12
- package/dist/integrations/nextjs.mjs +0 -18
- package/dist/logger/index.mjs +0 -56
- /package/dist/{chunk-J5AHUFP2.mjs → chunk-NKQPBSKX.js} +0 -0
- /package/dist/{cli.d.mts → cli.d.cts} +0 -0
- /package/dist/integrations/{express.d.mts → express.d.cts} +0 -0
- /package/dist/integrations/{fastify.d.mts → fastify.d.cts} +0 -0
- /package/dist/integrations/{nextjs.d.mts → nextjs.d.cts} +0 -0
- /package/dist/logger/{index.d.mts → index.d.cts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,2788 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
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
|
-
};
|
|
11
|
-
var __export = (target, all) => {
|
|
12
|
-
for (var name in all)
|
|
13
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
-
};
|
|
15
|
-
var __copyProps = (to, from, except, desc) => {
|
|
16
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
-
for (let key of __getOwnPropNames(from))
|
|
18
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
-
}
|
|
21
|
-
return to;
|
|
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
|
-
));
|
|
31
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
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
|
-
|
|
182
|
-
// src/index.ts
|
|
183
|
-
var index_exports = {};
|
|
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,
|
|
197
|
-
Statly: () => Statly,
|
|
198
|
-
StatlyClient: () => StatlyClient,
|
|
199
|
-
addBreadcrumb: () => addBreadcrumb,
|
|
200
|
-
captureException: () => captureException,
|
|
201
|
-
captureMessage: () => captureMessage,
|
|
202
|
-
captureNextJsError: () => captureNextJsError,
|
|
203
|
-
captureSpan: () => captureSpan,
|
|
204
|
-
close: () => close,
|
|
205
|
-
createRequestCapture: () => createRequestCapture,
|
|
206
|
-
expressErrorHandler: () => expressErrorHandler,
|
|
207
|
-
flush: () => flush,
|
|
208
|
-
formatJson: () => formatJson,
|
|
209
|
-
formatJsonPretty: () => formatJsonPretty,
|
|
210
|
-
formatPretty: () => formatPretty,
|
|
211
|
-
getClient: () => getClient,
|
|
212
|
-
getConsoleMethod: () => getConsoleMethod,
|
|
213
|
-
getDefaultLogger: () => getDefaultLogger,
|
|
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,
|
|
223
|
-
requestHandler: () => requestHandler,
|
|
224
|
-
setDefaultLogger: () => setDefaultLogger,
|
|
225
|
-
setTag: () => setTag,
|
|
226
|
-
setTags: () => setTags,
|
|
227
|
-
setUser: () => setUser,
|
|
228
|
-
startSpan: () => startSpan,
|
|
229
|
-
statlyFastifyPlugin: () => statlyFastifyPlugin,
|
|
230
|
-
statlyPlugin: () => statlyPlugin,
|
|
231
|
-
trace: () => trace3,
|
|
232
|
-
withStatly: () => withStatly,
|
|
233
|
-
withStatlyGetServerSideProps: () => withStatlyGetServerSideProps,
|
|
234
|
-
withStatlyGetStaticProps: () => withStatlyGetStaticProps,
|
|
235
|
-
withStatlyPagesApi: () => withStatlyPagesApi,
|
|
236
|
-
withStatlyServerAction: () => withStatlyServerAction
|
|
237
|
-
});
|
|
238
|
-
module.exports = __toCommonJS(index_exports);
|
|
239
|
-
|
|
240
|
-
// src/transport.ts
|
|
241
|
-
var Transport = class {
|
|
242
|
-
constructor(options) {
|
|
243
|
-
this.queue = [];
|
|
244
|
-
this.isSending = false;
|
|
245
|
-
this.maxQueueSize = 100;
|
|
246
|
-
this.flushInterval = 5e3;
|
|
247
|
-
this.dsn = options.dsn;
|
|
248
|
-
this.debug = options.debug ?? false;
|
|
249
|
-
this.endpoint = this.parseEndpoint(options.dsn);
|
|
250
|
-
this.startFlushTimer();
|
|
251
|
-
}
|
|
252
|
-
parseEndpoint(dsn) {
|
|
253
|
-
try {
|
|
254
|
-
const url = new URL(dsn);
|
|
255
|
-
return `${url.protocol}//${url.host}/api/v1/observe/ingest`;
|
|
256
|
-
} catch {
|
|
257
|
-
return `https://statly.live/api/v1/observe/ingest`;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
startFlushTimer() {
|
|
261
|
-
if (typeof window !== "undefined") {
|
|
262
|
-
this.flushTimer = setInterval(() => {
|
|
263
|
-
this.flush();
|
|
264
|
-
}, this.flushInterval);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Add an event to the queue
|
|
269
|
-
*/
|
|
270
|
-
enqueue(event) {
|
|
271
|
-
if (this.queue.length >= this.maxQueueSize) {
|
|
272
|
-
this.queue.shift();
|
|
273
|
-
if (this.debug) {
|
|
274
|
-
console.warn("[Statly] Event queue full, dropping oldest event");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
this.queue.push(event);
|
|
278
|
-
if (this.queue.length >= 10) {
|
|
279
|
-
this.flush();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Send a single event immediately
|
|
284
|
-
*/
|
|
285
|
-
async send(event) {
|
|
286
|
-
return this.sendBatch([event]);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Flush all queued events
|
|
290
|
-
*/
|
|
291
|
-
async flush() {
|
|
292
|
-
if (this.isSending || this.queue.length === 0) {
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
this.isSending = true;
|
|
296
|
-
const events = [...this.queue];
|
|
297
|
-
this.queue = [];
|
|
298
|
-
try {
|
|
299
|
-
await this.sendBatch(events);
|
|
300
|
-
} catch (error2) {
|
|
301
|
-
this.queue = [...events, ...this.queue].slice(0, this.maxQueueSize);
|
|
302
|
-
if (this.debug) {
|
|
303
|
-
console.error("[Statly] Failed to send events:", error2);
|
|
304
|
-
}
|
|
305
|
-
} finally {
|
|
306
|
-
this.isSending = false;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Send a batch of events
|
|
311
|
-
*/
|
|
312
|
-
async sendBatch(events) {
|
|
313
|
-
if (events.length === 0) {
|
|
314
|
-
return { success: true };
|
|
315
|
-
}
|
|
316
|
-
const payload = events.length === 1 ? events[0] : { events };
|
|
317
|
-
try {
|
|
318
|
-
const response = await fetch(this.endpoint, {
|
|
319
|
-
method: "POST",
|
|
320
|
-
headers: {
|
|
321
|
-
"Content-Type": "application/json",
|
|
322
|
-
"X-Statly-DSN": this.dsn
|
|
323
|
-
},
|
|
324
|
-
body: JSON.stringify(payload),
|
|
325
|
-
// Use keepalive for better reliability during page unload
|
|
326
|
-
keepalive: true
|
|
327
|
-
});
|
|
328
|
-
if (!response.ok) {
|
|
329
|
-
const errorText = await response.text().catch(() => "Unknown error");
|
|
330
|
-
if (this.debug) {
|
|
331
|
-
console.error("[Statly] API error:", response.status, errorText);
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
success: false,
|
|
335
|
-
status: response.status,
|
|
336
|
-
error: errorText
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
if (this.debug) {
|
|
340
|
-
console.log(`[Statly] Sent ${events.length} event(s)`);
|
|
341
|
-
}
|
|
342
|
-
return { success: true, status: response.status };
|
|
343
|
-
} catch (error2) {
|
|
344
|
-
if (this.debug) {
|
|
345
|
-
console.error("[Statly] Network error:", error2);
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
success: false,
|
|
349
|
-
error: error2 instanceof Error ? error2.message : "Network error"
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Clean up resources
|
|
355
|
-
*/
|
|
356
|
-
destroy() {
|
|
357
|
-
if (this.flushTimer) {
|
|
358
|
-
clearInterval(this.flushTimer);
|
|
359
|
-
}
|
|
360
|
-
this.flush();
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// src/breadcrumbs.ts
|
|
365
|
-
var BreadcrumbManager = class {
|
|
366
|
-
constructor(maxBreadcrumbs = 100) {
|
|
367
|
-
this.breadcrumbs = [];
|
|
368
|
-
this.maxBreadcrumbs = maxBreadcrumbs;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Add a breadcrumb
|
|
372
|
-
*/
|
|
373
|
-
add(breadcrumb) {
|
|
374
|
-
const crumb = {
|
|
375
|
-
timestamp: Date.now(),
|
|
376
|
-
...breadcrumb
|
|
377
|
-
};
|
|
378
|
-
this.breadcrumbs.push(crumb);
|
|
379
|
-
if (this.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
380
|
-
this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Get all breadcrumbs
|
|
385
|
-
*/
|
|
386
|
-
getAll() {
|
|
387
|
-
return [...this.breadcrumbs];
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Clear all breadcrumbs
|
|
391
|
-
*/
|
|
392
|
-
clear() {
|
|
393
|
-
this.breadcrumbs = [];
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Set maximum breadcrumbs to keep
|
|
397
|
-
*/
|
|
398
|
-
setMaxBreadcrumbs(max) {
|
|
399
|
-
this.maxBreadcrumbs = max;
|
|
400
|
-
if (this.breadcrumbs.length > max) {
|
|
401
|
-
this.breadcrumbs = this.breadcrumbs.slice(-max);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
// src/integrations/global-handlers.ts
|
|
407
|
-
var GlobalHandlers = class {
|
|
408
|
-
constructor(options = {}) {
|
|
409
|
-
this.originalOnError = null;
|
|
410
|
-
this.originalOnUnhandledRejection = null;
|
|
411
|
-
this.errorCallback = null;
|
|
412
|
-
this.handleUnhandledRejection = (event) => {
|
|
413
|
-
if (!this.errorCallback) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
let error2;
|
|
417
|
-
if (event.reason instanceof Error) {
|
|
418
|
-
error2 = event.reason;
|
|
419
|
-
} else if (typeof event.reason === "string") {
|
|
420
|
-
error2 = new Error(event.reason);
|
|
421
|
-
} else {
|
|
422
|
-
error2 = new Error("Unhandled Promise Rejection");
|
|
423
|
-
error2.reason = event.reason;
|
|
424
|
-
}
|
|
425
|
-
this.errorCallback(error2, {
|
|
426
|
-
mechanism: { type: "onunhandledrejection", handled: false }
|
|
427
|
-
});
|
|
428
|
-
};
|
|
429
|
-
this.options = {
|
|
430
|
-
onerror: options.onerror !== false,
|
|
431
|
-
onunhandledrejection: options.onunhandledrejection !== false
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Install global error handlers
|
|
436
|
-
*/
|
|
437
|
-
install(callback) {
|
|
438
|
-
this.errorCallback = callback;
|
|
439
|
-
if (typeof window === "undefined") {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (this.options.onerror) {
|
|
443
|
-
this.installOnError();
|
|
444
|
-
}
|
|
445
|
-
if (this.options.onunhandledrejection) {
|
|
446
|
-
this.installOnUnhandledRejection();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Uninstall global error handlers
|
|
451
|
-
*/
|
|
452
|
-
uninstall() {
|
|
453
|
-
if (typeof window === "undefined") {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
if (this.originalOnError !== null) {
|
|
457
|
-
window.onerror = this.originalOnError;
|
|
458
|
-
this.originalOnError = null;
|
|
459
|
-
}
|
|
460
|
-
if (this.originalOnUnhandledRejection !== null) {
|
|
461
|
-
window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
462
|
-
this.originalOnUnhandledRejection = null;
|
|
463
|
-
}
|
|
464
|
-
this.errorCallback = null;
|
|
465
|
-
}
|
|
466
|
-
installOnError() {
|
|
467
|
-
this.originalOnError = window.onerror;
|
|
468
|
-
window.onerror = (message, source, lineno, colno, error2) => {
|
|
469
|
-
if (this.originalOnError) {
|
|
470
|
-
this.originalOnError.call(window, message, source, lineno, colno, error2);
|
|
471
|
-
}
|
|
472
|
-
if (this.errorCallback) {
|
|
473
|
-
const errorObj = error2 || new Error(String(message));
|
|
474
|
-
if (!error2 && source) {
|
|
475
|
-
errorObj.filename = source;
|
|
476
|
-
errorObj.lineno = lineno;
|
|
477
|
-
errorObj.colno = colno;
|
|
478
|
-
}
|
|
479
|
-
this.errorCallback(errorObj, {
|
|
480
|
-
mechanism: { type: "onerror", handled: false },
|
|
481
|
-
source,
|
|
482
|
-
lineno,
|
|
483
|
-
colno
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
return false;
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
installOnUnhandledRejection() {
|
|
490
|
-
this.originalOnUnhandledRejection = this.handleUnhandledRejection.bind(this);
|
|
491
|
-
window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
// src/integrations/console.ts
|
|
496
|
-
var ConsoleIntegration = class {
|
|
497
|
-
constructor() {
|
|
498
|
-
this.originalMethods = {};
|
|
499
|
-
this.callback = null;
|
|
500
|
-
this.levels = ["debug", "info", "warn", "error", "log"];
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Install console breadcrumb tracking
|
|
504
|
-
*/
|
|
505
|
-
install(callback, levels) {
|
|
506
|
-
this.callback = callback;
|
|
507
|
-
if (levels) {
|
|
508
|
-
this.levels = levels;
|
|
509
|
-
}
|
|
510
|
-
if (typeof console === "undefined") {
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
for (const level of this.levels) {
|
|
514
|
-
this.wrapConsoleMethod(level);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Uninstall console breadcrumb tracking
|
|
519
|
-
*/
|
|
520
|
-
uninstall() {
|
|
521
|
-
if (typeof console === "undefined") {
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
for (const level of this.levels) {
|
|
525
|
-
if (this.originalMethods[level]) {
|
|
526
|
-
console[level] = this.originalMethods[level];
|
|
527
|
-
delete this.originalMethods[level];
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
this.callback = null;
|
|
531
|
-
}
|
|
532
|
-
wrapConsoleMethod(level) {
|
|
533
|
-
const originalMethod = console[level];
|
|
534
|
-
if (!originalMethod) {
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
this.originalMethods[level] = originalMethod;
|
|
538
|
-
const self = this;
|
|
539
|
-
console[level] = function(...args) {
|
|
540
|
-
if (self.callback) {
|
|
541
|
-
self.callback({
|
|
542
|
-
category: "console",
|
|
543
|
-
message: self.formatArgs(args),
|
|
544
|
-
level: self.mapLevel(level),
|
|
545
|
-
data: args.length > 1 ? { arguments: args } : void 0
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
originalMethod.apply(console, args);
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
formatArgs(args) {
|
|
552
|
-
return args.map((arg) => {
|
|
553
|
-
if (typeof arg === "string") {
|
|
554
|
-
return arg;
|
|
555
|
-
}
|
|
556
|
-
if (arg instanceof Error) {
|
|
557
|
-
return arg.message;
|
|
558
|
-
}
|
|
559
|
-
try {
|
|
560
|
-
return JSON.stringify(arg);
|
|
561
|
-
} catch {
|
|
562
|
-
return String(arg);
|
|
563
|
-
}
|
|
564
|
-
}).join(" ");
|
|
565
|
-
}
|
|
566
|
-
mapLevel(consoleLevel) {
|
|
567
|
-
switch (consoleLevel) {
|
|
568
|
-
case "debug":
|
|
569
|
-
return "debug";
|
|
570
|
-
case "info":
|
|
571
|
-
case "log":
|
|
572
|
-
return "info";
|
|
573
|
-
case "warn":
|
|
574
|
-
return "warning";
|
|
575
|
-
case "error":
|
|
576
|
-
return "error";
|
|
577
|
-
default:
|
|
578
|
-
return "info";
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
// src/client.ts
|
|
584
|
-
init_telemetry();
|
|
585
|
-
var SDK_NAME = "@statly/observe-sdk";
|
|
586
|
-
var SDK_VERSION = "0.1.0";
|
|
587
|
-
var StatlyClient = class {
|
|
588
|
-
constructor(options) {
|
|
589
|
-
this.user = null;
|
|
590
|
-
this.initialized = false;
|
|
591
|
-
this.options = this.mergeOptions(options);
|
|
592
|
-
this.transport = new Transport({
|
|
593
|
-
dsn: this.options.dsn,
|
|
594
|
-
debug: this.options.debug
|
|
595
|
-
});
|
|
596
|
-
this.breadcrumbs = new BreadcrumbManager(this.options.maxBreadcrumbs);
|
|
597
|
-
this.globalHandlers = new GlobalHandlers();
|
|
598
|
-
this.consoleIntegration = new ConsoleIntegration();
|
|
599
|
-
TelemetryProvider.getInstance().setClient(this);
|
|
600
|
-
}
|
|
601
|
-
mergeOptions(options) {
|
|
602
|
-
return {
|
|
603
|
-
dsn: options.dsn,
|
|
604
|
-
release: options.release ?? "",
|
|
605
|
-
environment: options.environment ?? this.detectEnvironment(),
|
|
606
|
-
debug: options.debug ?? false,
|
|
607
|
-
sampleRate: options.sampleRate ?? 1,
|
|
608
|
-
maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
|
|
609
|
-
autoCapture: options.autoCapture !== false,
|
|
610
|
-
captureConsole: options.captureConsole !== false,
|
|
611
|
-
captureNetwork: options.captureNetwork ?? false,
|
|
612
|
-
tags: options.tags ?? {},
|
|
613
|
-
beforeSend: options.beforeSend ?? ((e) => e)
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
detectEnvironment() {
|
|
617
|
-
if (typeof window !== "undefined") {
|
|
618
|
-
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
|
619
|
-
return "development";
|
|
620
|
-
}
|
|
621
|
-
if (window.location.hostname.includes("staging") || window.location.hostname.includes("stage")) {
|
|
622
|
-
return "staging";
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return "production";
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Initialize the SDK
|
|
629
|
-
*/
|
|
630
|
-
init() {
|
|
631
|
-
if (this.initialized) {
|
|
632
|
-
if (this.options.debug) {
|
|
633
|
-
console.warn("[Statly] SDK already initialized");
|
|
634
|
-
}
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
this.initialized = true;
|
|
638
|
-
if (this.options.autoCapture) {
|
|
639
|
-
this.globalHandlers.install((error2, context) => {
|
|
640
|
-
this.captureError(error2, context);
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
if (this.options.captureConsole) {
|
|
644
|
-
this.consoleIntegration.install((breadcrumb) => {
|
|
645
|
-
this.breadcrumbs.add(breadcrumb);
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
this.addBreadcrumb({
|
|
649
|
-
category: "navigation",
|
|
650
|
-
message: "SDK initialized",
|
|
651
|
-
level: "info"
|
|
652
|
-
});
|
|
653
|
-
if (this.options.debug) {
|
|
654
|
-
console.log("[Statly] SDK initialized", {
|
|
655
|
-
environment: this.options.environment,
|
|
656
|
-
release: this.options.release
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Capture an exception/error
|
|
662
|
-
*/
|
|
663
|
-
captureException(error2, context) {
|
|
664
|
-
let errorObj;
|
|
665
|
-
if (error2 instanceof Error) {
|
|
666
|
-
errorObj = error2;
|
|
667
|
-
} else if (typeof error2 === "string") {
|
|
668
|
-
errorObj = new Error(error2);
|
|
669
|
-
} else {
|
|
670
|
-
errorObj = new Error("Unknown error");
|
|
671
|
-
errorObj.originalError = error2;
|
|
672
|
-
}
|
|
673
|
-
return this.captureError(errorObj, context);
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Capture a message
|
|
677
|
-
*/
|
|
678
|
-
captureMessage(message, level = "info") {
|
|
679
|
-
const event = this.buildEvent({
|
|
680
|
-
message,
|
|
681
|
-
level
|
|
682
|
-
});
|
|
683
|
-
return this.sendEvent(event);
|
|
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
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Internal method to capture an error
|
|
711
|
-
*/
|
|
712
|
-
captureError(error2, context) {
|
|
713
|
-
if (Math.random() > this.options.sampleRate) {
|
|
714
|
-
return "";
|
|
715
|
-
}
|
|
716
|
-
const event = this.buildEvent({
|
|
717
|
-
message: error2.message,
|
|
718
|
-
level: "error",
|
|
719
|
-
stack: error2.stack,
|
|
720
|
-
exception: {
|
|
721
|
-
type: error2.name,
|
|
722
|
-
value: error2.message,
|
|
723
|
-
stacktrace: this.parseStackTrace(error2.stack)
|
|
724
|
-
},
|
|
725
|
-
extra: context
|
|
726
|
-
});
|
|
727
|
-
return this.sendEvent(event);
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Build a complete event from partial data
|
|
731
|
-
*/
|
|
732
|
-
buildEvent(partial) {
|
|
733
|
-
const event = {
|
|
734
|
-
message: partial.message || "Unknown error",
|
|
735
|
-
timestamp: Date.now(),
|
|
736
|
-
level: partial.level || "error",
|
|
737
|
-
environment: this.options.environment,
|
|
738
|
-
release: this.options.release || void 0,
|
|
739
|
-
url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
740
|
-
user: this.user || void 0,
|
|
741
|
-
tags: { ...this.options.tags, ...partial.tags },
|
|
742
|
-
extra: partial.extra,
|
|
743
|
-
breadcrumbs: this.breadcrumbs.getAll(),
|
|
744
|
-
browser: this.getBrowserInfo(),
|
|
745
|
-
os: this.getOSInfo(),
|
|
746
|
-
sdk: {
|
|
747
|
-
name: SDK_NAME,
|
|
748
|
-
version: SDK_VERSION
|
|
749
|
-
},
|
|
750
|
-
...partial
|
|
751
|
-
};
|
|
752
|
-
return event;
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Parse a stack trace string into structured frames
|
|
756
|
-
*/
|
|
757
|
-
parseStackTrace(stack) {
|
|
758
|
-
if (!stack) {
|
|
759
|
-
return void 0;
|
|
760
|
-
}
|
|
761
|
-
const frames = [];
|
|
762
|
-
const lines = stack.split("\n");
|
|
763
|
-
for (const line of lines) {
|
|
764
|
-
const chromeMatch = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
765
|
-
if (chromeMatch) {
|
|
766
|
-
frames.push({
|
|
767
|
-
function: chromeMatch[1] || "<anonymous>",
|
|
768
|
-
filename: chromeMatch[2],
|
|
769
|
-
lineno: parseInt(chromeMatch[3], 10),
|
|
770
|
-
colno: parseInt(chromeMatch[4], 10)
|
|
771
|
-
});
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
const firefoxMatch = line.match(/^(.+?)@(.+?):(\d+):(\d+)$/);
|
|
775
|
-
if (firefoxMatch) {
|
|
776
|
-
frames.push({
|
|
777
|
-
function: firefoxMatch[1] || "<anonymous>",
|
|
778
|
-
filename: firefoxMatch[2],
|
|
779
|
-
lineno: parseInt(firefoxMatch[3], 10),
|
|
780
|
-
colno: parseInt(firefoxMatch[4], 10)
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return frames.length > 0 ? { frames } : void 0;
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Send an event to the server
|
|
788
|
-
*/
|
|
789
|
-
sendEvent(event) {
|
|
790
|
-
const processed = this.options.beforeSend(event);
|
|
791
|
-
if (!processed) {
|
|
792
|
-
if (this.options.debug) {
|
|
793
|
-
console.log("[Statly] Event dropped by beforeSend");
|
|
794
|
-
}
|
|
795
|
-
return "";
|
|
796
|
-
}
|
|
797
|
-
const eventId = this.generateEventId();
|
|
798
|
-
this.breadcrumbs.add({
|
|
799
|
-
category: "statly",
|
|
800
|
-
message: `Captured ${event.level}: ${event.message.slice(0, 50)}`,
|
|
801
|
-
level: "info"
|
|
802
|
-
});
|
|
803
|
-
this.transport.enqueue(processed);
|
|
804
|
-
if (this.options.debug) {
|
|
805
|
-
console.log("[Statly] Event captured:", eventId, event.message);
|
|
806
|
-
}
|
|
807
|
-
return eventId;
|
|
808
|
-
}
|
|
809
|
-
generateEventId() {
|
|
810
|
-
return crypto.randomUUID?.() || "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
811
|
-
const r = Math.random() * 16 | 0;
|
|
812
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
813
|
-
return v.toString(16);
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
/**
|
|
817
|
-
* Set user context
|
|
818
|
-
*/
|
|
819
|
-
setUser(user) {
|
|
820
|
-
this.user = user;
|
|
821
|
-
if (this.options.debug && user) {
|
|
822
|
-
console.log("[Statly] User set:", user.id || user.email);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Set a single tag
|
|
827
|
-
*/
|
|
828
|
-
setTag(key, value) {
|
|
829
|
-
this.options.tags[key] = value;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Set multiple tags
|
|
833
|
-
*/
|
|
834
|
-
setTags(tags) {
|
|
835
|
-
Object.assign(this.options.tags, tags);
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Add a breadcrumb
|
|
839
|
-
*/
|
|
840
|
-
addBreadcrumb(breadcrumb) {
|
|
841
|
-
this.breadcrumbs.add(breadcrumb);
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Get browser info
|
|
845
|
-
*/
|
|
846
|
-
getBrowserInfo() {
|
|
847
|
-
if (typeof navigator === "undefined") {
|
|
848
|
-
return void 0;
|
|
849
|
-
}
|
|
850
|
-
const ua = navigator.userAgent;
|
|
851
|
-
let name = "Unknown";
|
|
852
|
-
let version = "";
|
|
853
|
-
if (ua.includes("Firefox/")) {
|
|
854
|
-
name = "Firefox";
|
|
855
|
-
version = ua.split("Firefox/")[1]?.split(" ")[0] || "";
|
|
856
|
-
} else if (ua.includes("Chrome/")) {
|
|
857
|
-
name = "Chrome";
|
|
858
|
-
version = ua.split("Chrome/")[1]?.split(" ")[0] || "";
|
|
859
|
-
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
860
|
-
name = "Safari";
|
|
861
|
-
version = ua.split("Version/")[1]?.split(" ")[0] || "";
|
|
862
|
-
} else if (ua.includes("Edge/") || ua.includes("Edg/")) {
|
|
863
|
-
name = "Edge";
|
|
864
|
-
version = ua.split(/Edg?e?\//)[1]?.split(" ")[0] || "";
|
|
865
|
-
}
|
|
866
|
-
return { name, version };
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Get OS info
|
|
870
|
-
*/
|
|
871
|
-
getOSInfo() {
|
|
872
|
-
if (typeof navigator === "undefined") {
|
|
873
|
-
return void 0;
|
|
874
|
-
}
|
|
875
|
-
const ua = navigator.userAgent;
|
|
876
|
-
let name = "Unknown";
|
|
877
|
-
let version = "";
|
|
878
|
-
if (ua.includes("Windows")) {
|
|
879
|
-
name = "Windows";
|
|
880
|
-
const match = ua.match(/Windows NT (\d+\.\d+)/);
|
|
881
|
-
if (match) version = match[1];
|
|
882
|
-
} else if (ua.includes("Mac OS X")) {
|
|
883
|
-
name = "macOS";
|
|
884
|
-
const match = ua.match(/Mac OS X (\d+[._]\d+)/);
|
|
885
|
-
if (match) version = match[1].replace("_", ".");
|
|
886
|
-
} else if (ua.includes("Linux")) {
|
|
887
|
-
name = "Linux";
|
|
888
|
-
} else if (ua.includes("Android")) {
|
|
889
|
-
name = "Android";
|
|
890
|
-
const match = ua.match(/Android (\d+\.\d+)/);
|
|
891
|
-
if (match) version = match[1];
|
|
892
|
-
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
893
|
-
name = "iOS";
|
|
894
|
-
const match = ua.match(/OS (\d+_\d+)/);
|
|
895
|
-
if (match) version = match[1].replace("_", ".");
|
|
896
|
-
}
|
|
897
|
-
return { name, version };
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Flush pending events and clean up
|
|
901
|
-
*/
|
|
902
|
-
async close() {
|
|
903
|
-
this.globalHandlers.uninstall();
|
|
904
|
-
this.consoleIntegration.uninstall();
|
|
905
|
-
await this.transport.flush();
|
|
906
|
-
this.transport.destroy();
|
|
907
|
-
this.initialized = false;
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Force flush pending events
|
|
911
|
-
*/
|
|
912
|
-
async flush() {
|
|
913
|
-
await this.transport.flush();
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
// src/integrations/express.ts
|
|
918
|
-
function requestHandler() {
|
|
919
|
-
return (req, res, next) => {
|
|
920
|
-
req.statlyContext = {
|
|
921
|
-
transactionName: `${req.method} ${req.path || req.url}`,
|
|
922
|
-
startTime: Date.now()
|
|
923
|
-
};
|
|
924
|
-
Statly.addBreadcrumb({
|
|
925
|
-
category: "http",
|
|
926
|
-
message: `${req.method} ${req.originalUrl || req.url}`,
|
|
927
|
-
level: "info",
|
|
928
|
-
data: {
|
|
929
|
-
method: req.method,
|
|
930
|
-
url: req.originalUrl || req.url
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
if (req.user) {
|
|
934
|
-
Statly.setUser({
|
|
935
|
-
id: req.user.id?.toString(),
|
|
936
|
-
email: req.user.email?.toString()
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
res.on("finish", () => {
|
|
940
|
-
const duration = req.statlyContext?.startTime ? Date.now() - req.statlyContext.startTime : void 0;
|
|
941
|
-
Statly.addBreadcrumb({
|
|
942
|
-
category: "http",
|
|
943
|
-
message: `Response ${res.statusCode}`,
|
|
944
|
-
level: res.statusCode >= 400 ? "error" : "info",
|
|
945
|
-
data: {
|
|
946
|
-
statusCode: res.statusCode,
|
|
947
|
-
duration
|
|
948
|
-
}
|
|
949
|
-
});
|
|
950
|
-
});
|
|
951
|
-
next();
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
function expressErrorHandler(options = {}) {
|
|
955
|
-
return (err, req, res, next) => {
|
|
956
|
-
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
957
|
-
if (options.shouldHandleError && !options.shouldHandleError(error2)) {
|
|
958
|
-
return next(err);
|
|
959
|
-
}
|
|
960
|
-
const context = {
|
|
961
|
-
request: {
|
|
962
|
-
method: req.method,
|
|
963
|
-
url: req.originalUrl || req.url,
|
|
964
|
-
headers: sanitizeHeaders(req.headers),
|
|
965
|
-
query: req.query,
|
|
966
|
-
data: sanitizeBody(req.body)
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
if (req.ip) {
|
|
970
|
-
context.ip = req.ip;
|
|
971
|
-
}
|
|
972
|
-
if (req.user) {
|
|
973
|
-
Statly.setUser({
|
|
974
|
-
id: req.user.id?.toString(),
|
|
975
|
-
email: req.user.email?.toString()
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
if (req.statlyContext?.transactionName) {
|
|
979
|
-
Statly.setTag("transaction", req.statlyContext.transactionName);
|
|
980
|
-
}
|
|
981
|
-
Statly.captureException(error2, context);
|
|
982
|
-
next(err);
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
function sanitizeHeaders(headers) {
|
|
986
|
-
const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
|
|
987
|
-
const sanitized = {};
|
|
988
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
989
|
-
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
990
|
-
sanitized[key] = "[Filtered]";
|
|
991
|
-
} else {
|
|
992
|
-
sanitized[key] = value;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return sanitized;
|
|
996
|
-
}
|
|
997
|
-
function sanitizeBody(body) {
|
|
998
|
-
if (!body || typeof body !== "object") {
|
|
999
|
-
return body;
|
|
1000
|
-
}
|
|
1001
|
-
const sensitiveFields = ["password", "secret", "token", "apiKey", "api_key", "credit_card", "creditCard", "ssn"];
|
|
1002
|
-
const sanitized = {};
|
|
1003
|
-
for (const [key, value] of Object.entries(body)) {
|
|
1004
|
-
if (sensitiveFields.some((field) => key.toLowerCase().includes(field.toLowerCase()))) {
|
|
1005
|
-
sanitized[key] = "[Filtered]";
|
|
1006
|
-
} else if (typeof value === "object" && value !== null) {
|
|
1007
|
-
sanitized[key] = sanitizeBody(value);
|
|
1008
|
-
} else {
|
|
1009
|
-
sanitized[key] = value;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return sanitized;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// src/integrations/nextjs.ts
|
|
1016
|
-
function withStatlyPagesApi(handler) {
|
|
1017
|
-
return async (req, res) => {
|
|
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: {
|
|
1027
|
-
method: req.method,
|
|
1028
|
-
url: req.url
|
|
1029
|
-
}
|
|
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
|
-
});
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
function withStatly(handler) {
|
|
1050
|
-
const wrappedHandler = async (request, context) => {
|
|
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: {
|
|
1060
|
-
method: request.method,
|
|
1061
|
-
url: request.nextUrl?.pathname || request.url
|
|
1062
|
-
}
|
|
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
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
Statly.captureException(error2, errorContext);
|
|
1090
|
-
throw error2;
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
};
|
|
1094
|
-
return wrappedHandler;
|
|
1095
|
-
}
|
|
1096
|
-
function captureNextJsError(error2, context) {
|
|
1097
|
-
return Statly.captureException(error2, {
|
|
1098
|
-
...context,
|
|
1099
|
-
digest: error2.digest,
|
|
1100
|
-
source: "nextjs-error-boundary"
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
function withStatlyGetServerSideProps(handler) {
|
|
1104
|
-
return async (context) => {
|
|
1105
|
-
try {
|
|
1106
|
-
return await handler(context);
|
|
1107
|
-
} catch (error2) {
|
|
1108
|
-
Statly.captureException(error2, {
|
|
1109
|
-
source: "getServerSideProps",
|
|
1110
|
-
url: context.req?.url || context.resolvedUrl
|
|
1111
|
-
});
|
|
1112
|
-
throw error2;
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
function withStatlyGetStaticProps(handler) {
|
|
1117
|
-
return async (context) => {
|
|
1118
|
-
try {
|
|
1119
|
-
return await handler(context);
|
|
1120
|
-
} catch (error2) {
|
|
1121
|
-
Statly.captureException(error2, {
|
|
1122
|
-
source: "getStaticProps",
|
|
1123
|
-
params: context.params
|
|
1124
|
-
});
|
|
1125
|
-
throw error2;
|
|
1126
|
-
}
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
function withStatlyServerAction(action, actionName) {
|
|
1130
|
-
return async (...args) => {
|
|
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"
|
|
1138
|
-
});
|
|
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
|
-
});
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
function sanitizeHeaders2(headers) {
|
|
1152
|
-
const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
|
|
1153
|
-
const sanitized = {};
|
|
1154
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1155
|
-
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
1156
|
-
sanitized[key] = "[Filtered]";
|
|
1157
|
-
} else {
|
|
1158
|
-
sanitized[key] = value;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
return sanitized;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// src/integrations/fastify.ts
|
|
1165
|
-
function statlyFastifyPlugin(fastify, options, done) {
|
|
1166
|
-
const {
|
|
1167
|
-
captureValidationErrors = true,
|
|
1168
|
-
shouldCapture,
|
|
1169
|
-
skipStatusCodes = [400, 401, 403, 404]
|
|
1170
|
-
} = options;
|
|
1171
|
-
fastify.addHook("onRequest", (request, _reply, hookDone) => {
|
|
1172
|
-
request.statlyStartTime = Date.now();
|
|
1173
|
-
Statly.addBreadcrumb({
|
|
1174
|
-
category: "http",
|
|
1175
|
-
message: `${request.method} ${request.routerPath || request.url}`,
|
|
1176
|
-
level: "info",
|
|
1177
|
-
data: {
|
|
1178
|
-
method: request.method,
|
|
1179
|
-
url: request.url,
|
|
1180
|
-
routerPath: request.routerPath,
|
|
1181
|
-
requestId: request.id
|
|
1182
|
-
}
|
|
1183
|
-
});
|
|
1184
|
-
hookDone();
|
|
1185
|
-
});
|
|
1186
|
-
fastify.addHook("onResponse", (request, reply, hookDone) => {
|
|
1187
|
-
const startTime = request.statlyStartTime;
|
|
1188
|
-
const duration = startTime ? Date.now() - startTime : void 0;
|
|
1189
|
-
Statly.addBreadcrumb({
|
|
1190
|
-
category: "http",
|
|
1191
|
-
message: `Response ${reply.statusCode}`,
|
|
1192
|
-
level: reply.statusCode >= 400 ? "error" : "info",
|
|
1193
|
-
data: {
|
|
1194
|
-
statusCode: reply.statusCode,
|
|
1195
|
-
duration,
|
|
1196
|
-
requestId: request.id
|
|
1197
|
-
}
|
|
1198
|
-
});
|
|
1199
|
-
hookDone();
|
|
1200
|
-
});
|
|
1201
|
-
fastify.setErrorHandler((error2, request, reply) => {
|
|
1202
|
-
const statusCode = error2.statusCode || 500;
|
|
1203
|
-
if (skipStatusCodes.includes(statusCode)) {
|
|
1204
|
-
throw error2;
|
|
1205
|
-
}
|
|
1206
|
-
if (!captureValidationErrors && error2.validation) {
|
|
1207
|
-
throw error2;
|
|
1208
|
-
}
|
|
1209
|
-
if (shouldCapture && !shouldCapture(error2)) {
|
|
1210
|
-
throw error2;
|
|
1211
|
-
}
|
|
1212
|
-
const context = {
|
|
1213
|
-
request: {
|
|
1214
|
-
id: request.id,
|
|
1215
|
-
method: request.method,
|
|
1216
|
-
url: request.url,
|
|
1217
|
-
routerPath: request.routerPath,
|
|
1218
|
-
headers: sanitizeHeaders3(request.headers),
|
|
1219
|
-
query: request.query,
|
|
1220
|
-
params: request.params
|
|
1221
|
-
},
|
|
1222
|
-
error: {
|
|
1223
|
-
statusCode: error2.statusCode,
|
|
1224
|
-
code: error2.code
|
|
1225
|
-
}
|
|
1226
|
-
};
|
|
1227
|
-
if (request.ip) {
|
|
1228
|
-
context.ip = request.ip;
|
|
1229
|
-
}
|
|
1230
|
-
if (error2.validation) {
|
|
1231
|
-
context.validation = error2.validation;
|
|
1232
|
-
}
|
|
1233
|
-
Statly.setTag("http.method", request.method);
|
|
1234
|
-
Statly.setTag("http.url", request.routerPath || request.url);
|
|
1235
|
-
Statly.setTag("http.status_code", String(statusCode));
|
|
1236
|
-
Statly.captureException(error2, context);
|
|
1237
|
-
throw error2;
|
|
1238
|
-
});
|
|
1239
|
-
done();
|
|
1240
|
-
}
|
|
1241
|
-
var statlyPlugin = statlyFastifyPlugin;
|
|
1242
|
-
function createRequestCapture(request) {
|
|
1243
|
-
return (error2, additionalContext) => {
|
|
1244
|
-
const context = {
|
|
1245
|
-
request: {
|
|
1246
|
-
id: request.id,
|
|
1247
|
-
method: request.method,
|
|
1248
|
-
url: request.url,
|
|
1249
|
-
routerPath: request.routerPath
|
|
1250
|
-
},
|
|
1251
|
-
...additionalContext
|
|
1252
|
-
};
|
|
1253
|
-
return Statly.captureException(error2, context);
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
function sanitizeHeaders3(headers) {
|
|
1257
|
-
const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
|
|
1258
|
-
const sanitized = {};
|
|
1259
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1260
|
-
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
1261
|
-
sanitized[key] = "[Filtered]";
|
|
1262
|
-
} else {
|
|
1263
|
-
sanitized[key] = value;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
return sanitized;
|
|
1267
|
-
}
|
|
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
|
-
|
|
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,
|
|
1
|
+
import {
|
|
2
|
+
Statly,
|
|
3
|
+
StatlyClient,
|
|
4
|
+
addBreadcrumb,
|
|
2771
5
|
captureException,
|
|
2772
6
|
captureMessage,
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
setTags,
|
|
2776
|
-
addBreadcrumb,
|
|
2777
|
-
flush,
|
|
7
|
+
captureNextJsError,
|
|
8
|
+
captureSpan,
|
|
2778
9
|
close,
|
|
10
|
+
createRequestCapture,
|
|
11
|
+
expressErrorHandler,
|
|
12
|
+
flush,
|
|
2779
13
|
getClient,
|
|
2780
|
-
|
|
14
|
+
init,
|
|
15
|
+
requestHandler,
|
|
16
|
+
setTag,
|
|
17
|
+
setTags,
|
|
18
|
+
setUser,
|
|
2781
19
|
startSpan,
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
20
|
+
statlyFastifyPlugin,
|
|
21
|
+
statlyPlugin,
|
|
22
|
+
trace as trace2,
|
|
23
|
+
withStatly,
|
|
24
|
+
withStatlyGetServerSideProps,
|
|
25
|
+
withStatlyGetStaticProps,
|
|
26
|
+
withStatlyPagesApi,
|
|
27
|
+
withStatlyServerAction
|
|
28
|
+
} from "./chunk-RLSLJL6N.js";
|
|
29
|
+
import {
|
|
30
|
+
AIFeatures,
|
|
31
|
+
ConsoleDestination,
|
|
32
|
+
DEFAULT_LEVELS,
|
|
33
|
+
EXTENDED_LEVELS,
|
|
34
|
+
FileDestination,
|
|
35
|
+
LOG_LEVELS,
|
|
36
|
+
Logger,
|
|
37
|
+
ObserveDestination,
|
|
38
|
+
REDACTED,
|
|
39
|
+
SCRUB_PATTERNS,
|
|
40
|
+
SENSITIVE_KEYS,
|
|
41
|
+
Scrubber,
|
|
42
|
+
audit,
|
|
43
|
+
debug,
|
|
44
|
+
error,
|
|
45
|
+
fatal,
|
|
46
|
+
formatJson,
|
|
47
|
+
formatJsonPretty,
|
|
48
|
+
formatPretty,
|
|
49
|
+
getConsoleMethod,
|
|
50
|
+
getDefaultLogger,
|
|
51
|
+
info,
|
|
52
|
+
isSensitiveKey,
|
|
53
|
+
setDefaultLogger,
|
|
54
|
+
trace,
|
|
55
|
+
warn
|
|
56
|
+
} from "./chunk-RUEXBTHM.js";
|
|
57
|
+
import "./chunk-NKQPBSKX.js";
|
|
58
|
+
export {
|
|
2786
59
|
AIFeatures,
|
|
2787
60
|
ConsoleDestination,
|
|
2788
61
|
DEFAULT_LEVELS,
|
|
@@ -2814,13 +87,13 @@ var Statly = {
|
|
|
2814
87
|
getDefaultLogger,
|
|
2815
88
|
init,
|
|
2816
89
|
isSensitiveKey,
|
|
2817
|
-
logAudit,
|
|
2818
|
-
logDebug,
|
|
2819
|
-
logError,
|
|
2820
|
-
logFatal,
|
|
2821
|
-
logInfo,
|
|
2822
|
-
logTrace,
|
|
2823
|
-
logWarn,
|
|
90
|
+
audit as logAudit,
|
|
91
|
+
debug as logDebug,
|
|
92
|
+
error as logError,
|
|
93
|
+
fatal as logFatal,
|
|
94
|
+
info as logInfo,
|
|
95
|
+
trace as logTrace,
|
|
96
|
+
warn as logWarn,
|
|
2824
97
|
requestHandler,
|
|
2825
98
|
setDefaultLogger,
|
|
2826
99
|
setTag,
|
|
@@ -2829,10 +102,10 @@ var Statly = {
|
|
|
2829
102
|
startSpan,
|
|
2830
103
|
statlyFastifyPlugin,
|
|
2831
104
|
statlyPlugin,
|
|
2832
|
-
trace,
|
|
105
|
+
trace2 as trace,
|
|
2833
106
|
withStatly,
|
|
2834
107
|
withStatlyGetServerSideProps,
|
|
2835
108
|
withStatlyGetStaticProps,
|
|
2836
109
|
withStatlyPagesApi,
|
|
2837
110
|
withStatlyServerAction
|
|
2838
|
-
}
|
|
111
|
+
};
|