@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
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
|
|
23
|
+
// src/span.ts
|
|
24
|
+
var Span, TraceContext;
|
|
25
|
+
var init_span = __esm({
|
|
26
|
+
"src/span.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
Span = class {
|
|
29
|
+
constructor(name, context, tags) {
|
|
30
|
+
this._status = "ok" /* OK */;
|
|
31
|
+
this._tags = {};
|
|
32
|
+
this._metadata = {};
|
|
33
|
+
this._finished = false;
|
|
34
|
+
this.name = name;
|
|
35
|
+
this.context = context;
|
|
36
|
+
this.startTime = Date.now();
|
|
37
|
+
if (tags) this._tags = { ...tags };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Finish the span and calculate duration
|
|
41
|
+
*/
|
|
42
|
+
finish(endTime) {
|
|
43
|
+
if (this._finished) return;
|
|
44
|
+
this._endTime = endTime || Date.now();
|
|
45
|
+
this._durationMs = this._endTime - this.startTime;
|
|
46
|
+
this._finished = true;
|
|
47
|
+
}
|
|
48
|
+
setTag(key, value) {
|
|
49
|
+
this._tags[key] = value;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
setMetadata(key, value) {
|
|
53
|
+
this._metadata[key] = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
setStatus(status) {
|
|
57
|
+
this._status = status;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
get status() {
|
|
61
|
+
return this._status;
|
|
62
|
+
}
|
|
63
|
+
get tags() {
|
|
64
|
+
return { ...this._tags };
|
|
65
|
+
}
|
|
66
|
+
get durationMs() {
|
|
67
|
+
return this._durationMs;
|
|
68
|
+
}
|
|
69
|
+
toDict() {
|
|
70
|
+
return {
|
|
71
|
+
name: this.name,
|
|
72
|
+
traceId: this.context.traceId,
|
|
73
|
+
spanId: this.context.spanId,
|
|
74
|
+
parentId: this.context.parentId,
|
|
75
|
+
startTime: this.startTime,
|
|
76
|
+
endTime: this._endTime,
|
|
77
|
+
durationMs: this._durationMs,
|
|
78
|
+
status: this._status,
|
|
79
|
+
tags: this._tags,
|
|
80
|
+
metadata: this._metadata
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
TraceContext = class {
|
|
85
|
+
static getActiveSpan() {
|
|
86
|
+
return this.currentSpan;
|
|
87
|
+
}
|
|
88
|
+
static setActiveSpan(span) {
|
|
89
|
+
this.currentSpan = span;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
TraceContext.currentSpan = null;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/telemetry.ts
|
|
97
|
+
var telemetry_exports = {};
|
|
98
|
+
__export(telemetry_exports, {
|
|
99
|
+
TelemetryProvider: () => TelemetryProvider,
|
|
100
|
+
trace: () => trace
|
|
101
|
+
});
|
|
102
|
+
async function trace(name, operation, tags) {
|
|
103
|
+
const provider = TelemetryProvider.getInstance();
|
|
104
|
+
const span = provider.startSpan(name, tags);
|
|
105
|
+
try {
|
|
106
|
+
const result = await operation(span);
|
|
107
|
+
return result;
|
|
108
|
+
} catch (error2) {
|
|
109
|
+
span.setStatus("error" /* ERROR */);
|
|
110
|
+
span.setTag("error", "true");
|
|
111
|
+
if (error2 instanceof Error) {
|
|
112
|
+
span.setTag("exception.type", error2.name);
|
|
113
|
+
span.setTag("exception.message", error2.message);
|
|
114
|
+
}
|
|
115
|
+
throw error2;
|
|
116
|
+
} finally {
|
|
117
|
+
provider.finishSpan(span);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
var TelemetryProvider;
|
|
121
|
+
var init_telemetry = __esm({
|
|
122
|
+
"src/telemetry.ts"() {
|
|
123
|
+
"use strict";
|
|
124
|
+
init_span();
|
|
125
|
+
TelemetryProvider = class _TelemetryProvider {
|
|
126
|
+
constructor() {
|
|
127
|
+
this.client = null;
|
|
128
|
+
}
|
|
129
|
+
static getInstance() {
|
|
130
|
+
if (!_TelemetryProvider.instance) {
|
|
131
|
+
_TelemetryProvider.instance = new _TelemetryProvider();
|
|
132
|
+
}
|
|
133
|
+
return _TelemetryProvider.instance;
|
|
134
|
+
}
|
|
135
|
+
setClient(client2) {
|
|
136
|
+
this.client = client2;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Start a new span
|
|
140
|
+
*/
|
|
141
|
+
startSpan(name, tags) {
|
|
142
|
+
const parent = TraceContext.getActiveSpan();
|
|
143
|
+
const traceId = parent ? parent.context.traceId : this.generateId();
|
|
144
|
+
const parentId = parent ? parent.context.spanId : null;
|
|
145
|
+
const span = new Span(name, {
|
|
146
|
+
traceId,
|
|
147
|
+
spanId: this.generateId(),
|
|
148
|
+
parentId
|
|
149
|
+
}, tags);
|
|
150
|
+
TraceContext.setActiveSpan(span);
|
|
151
|
+
return span;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Finish and report a span
|
|
155
|
+
*/
|
|
156
|
+
finishSpan(span) {
|
|
157
|
+
span.finish();
|
|
158
|
+
if (TraceContext.getActiveSpan() === span) {
|
|
159
|
+
TraceContext.setActiveSpan(null);
|
|
160
|
+
}
|
|
161
|
+
if (this.client) {
|
|
162
|
+
this.client.captureSpan(span);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
generateId() {
|
|
166
|
+
return Math.random().toString(16).substring(2, 18);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/integrations/fastify.ts
|
|
173
|
+
var fastify_exports = {};
|
|
174
|
+
__export(fastify_exports, {
|
|
175
|
+
createRequestCapture: () => createRequestCapture,
|
|
176
|
+
statlyFastifyPlugin: () => statlyFastifyPlugin,
|
|
177
|
+
statlyPlugin: () => statlyPlugin
|
|
178
|
+
});
|
|
179
|
+
module.exports = __toCommonJS(fastify_exports);
|
|
180
|
+
|
|
181
|
+
// src/transport.ts
|
|
182
|
+
var Transport = class {
|
|
183
|
+
constructor(options) {
|
|
184
|
+
this.queue = [];
|
|
185
|
+
this.isSending = false;
|
|
186
|
+
this.maxQueueSize = 100;
|
|
187
|
+
this.flushInterval = 5e3;
|
|
188
|
+
this.dsn = options.dsn;
|
|
189
|
+
this.debug = options.debug ?? false;
|
|
190
|
+
this.endpoint = this.parseEndpoint(options.dsn);
|
|
191
|
+
this.startFlushTimer();
|
|
192
|
+
}
|
|
193
|
+
parseEndpoint(dsn) {
|
|
194
|
+
try {
|
|
195
|
+
const url = new URL(dsn);
|
|
196
|
+
return `${url.protocol}//${url.host}/api/v1/observe/ingest`;
|
|
197
|
+
} catch {
|
|
198
|
+
return `https://statly.live/api/v1/observe/ingest`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
startFlushTimer() {
|
|
202
|
+
if (typeof window !== "undefined") {
|
|
203
|
+
this.flushTimer = setInterval(() => {
|
|
204
|
+
this.flush();
|
|
205
|
+
}, this.flushInterval);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Add an event to the queue
|
|
210
|
+
*/
|
|
211
|
+
enqueue(event) {
|
|
212
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
213
|
+
this.queue.shift();
|
|
214
|
+
if (this.debug) {
|
|
215
|
+
console.warn("[Statly] Event queue full, dropping oldest event");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
this.queue.push(event);
|
|
219
|
+
if (this.queue.length >= 10) {
|
|
220
|
+
this.flush();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Send a single event immediately
|
|
225
|
+
*/
|
|
226
|
+
async send(event) {
|
|
227
|
+
return this.sendBatch([event]);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Flush all queued events
|
|
231
|
+
*/
|
|
232
|
+
async flush() {
|
|
233
|
+
if (this.isSending || this.queue.length === 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.isSending = true;
|
|
237
|
+
const events = [...this.queue];
|
|
238
|
+
this.queue = [];
|
|
239
|
+
try {
|
|
240
|
+
await this.sendBatch(events);
|
|
241
|
+
} catch (error2) {
|
|
242
|
+
this.queue = [...events, ...this.queue].slice(0, this.maxQueueSize);
|
|
243
|
+
if (this.debug) {
|
|
244
|
+
console.error("[Statly] Failed to send events:", error2);
|
|
245
|
+
}
|
|
246
|
+
} finally {
|
|
247
|
+
this.isSending = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Send a batch of events
|
|
252
|
+
*/
|
|
253
|
+
async sendBatch(events) {
|
|
254
|
+
if (events.length === 0) {
|
|
255
|
+
return { success: true };
|
|
256
|
+
}
|
|
257
|
+
const payload = events.length === 1 ? events[0] : { events };
|
|
258
|
+
try {
|
|
259
|
+
const response = await fetch(this.endpoint, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
"X-Statly-DSN": this.dsn
|
|
264
|
+
},
|
|
265
|
+
body: JSON.stringify(payload),
|
|
266
|
+
// Use keepalive for better reliability during page unload
|
|
267
|
+
keepalive: true
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
271
|
+
if (this.debug) {
|
|
272
|
+
console.error("[Statly] API error:", response.status, errorText);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
status: response.status,
|
|
277
|
+
error: errorText
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (this.debug) {
|
|
281
|
+
console.log(`[Statly] Sent ${events.length} event(s)`);
|
|
282
|
+
}
|
|
283
|
+
return { success: true, status: response.status };
|
|
284
|
+
} catch (error2) {
|
|
285
|
+
if (this.debug) {
|
|
286
|
+
console.error("[Statly] Network error:", error2);
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: error2 instanceof Error ? error2.message : "Network error"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Clean up resources
|
|
296
|
+
*/
|
|
297
|
+
destroy() {
|
|
298
|
+
if (this.flushTimer) {
|
|
299
|
+
clearInterval(this.flushTimer);
|
|
300
|
+
}
|
|
301
|
+
this.flush();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/breadcrumbs.ts
|
|
306
|
+
var BreadcrumbManager = class {
|
|
307
|
+
constructor(maxBreadcrumbs = 100) {
|
|
308
|
+
this.breadcrumbs = [];
|
|
309
|
+
this.maxBreadcrumbs = maxBreadcrumbs;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Add a breadcrumb
|
|
313
|
+
*/
|
|
314
|
+
add(breadcrumb) {
|
|
315
|
+
const crumb = {
|
|
316
|
+
timestamp: Date.now(),
|
|
317
|
+
...breadcrumb
|
|
318
|
+
};
|
|
319
|
+
this.breadcrumbs.push(crumb);
|
|
320
|
+
if (this.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
321
|
+
this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get all breadcrumbs
|
|
326
|
+
*/
|
|
327
|
+
getAll() {
|
|
328
|
+
return [...this.breadcrumbs];
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Clear all breadcrumbs
|
|
332
|
+
*/
|
|
333
|
+
clear() {
|
|
334
|
+
this.breadcrumbs = [];
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Set maximum breadcrumbs to keep
|
|
338
|
+
*/
|
|
339
|
+
setMaxBreadcrumbs(max) {
|
|
340
|
+
this.maxBreadcrumbs = max;
|
|
341
|
+
if (this.breadcrumbs.length > max) {
|
|
342
|
+
this.breadcrumbs = this.breadcrumbs.slice(-max);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/integrations/global-handlers.ts
|
|
348
|
+
var GlobalHandlers = class {
|
|
349
|
+
constructor(options = {}) {
|
|
350
|
+
this.originalOnError = null;
|
|
351
|
+
this.originalOnUnhandledRejection = null;
|
|
352
|
+
this.errorCallback = null;
|
|
353
|
+
this.handleUnhandledRejection = (event) => {
|
|
354
|
+
if (!this.errorCallback) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
let error2;
|
|
358
|
+
if (event.reason instanceof Error) {
|
|
359
|
+
error2 = event.reason;
|
|
360
|
+
} else if (typeof event.reason === "string") {
|
|
361
|
+
error2 = new Error(event.reason);
|
|
362
|
+
} else {
|
|
363
|
+
error2 = new Error("Unhandled Promise Rejection");
|
|
364
|
+
error2.reason = event.reason;
|
|
365
|
+
}
|
|
366
|
+
this.errorCallback(error2, {
|
|
367
|
+
mechanism: { type: "onunhandledrejection", handled: false }
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
this.options = {
|
|
371
|
+
onerror: options.onerror !== false,
|
|
372
|
+
onunhandledrejection: options.onunhandledrejection !== false
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Install global error handlers
|
|
377
|
+
*/
|
|
378
|
+
install(callback) {
|
|
379
|
+
this.errorCallback = callback;
|
|
380
|
+
if (typeof window === "undefined") {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (this.options.onerror) {
|
|
384
|
+
this.installOnError();
|
|
385
|
+
}
|
|
386
|
+
if (this.options.onunhandledrejection) {
|
|
387
|
+
this.installOnUnhandledRejection();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Uninstall global error handlers
|
|
392
|
+
*/
|
|
393
|
+
uninstall() {
|
|
394
|
+
if (typeof window === "undefined") {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (this.originalOnError !== null) {
|
|
398
|
+
window.onerror = this.originalOnError;
|
|
399
|
+
this.originalOnError = null;
|
|
400
|
+
}
|
|
401
|
+
if (this.originalOnUnhandledRejection !== null) {
|
|
402
|
+
window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
403
|
+
this.originalOnUnhandledRejection = null;
|
|
404
|
+
}
|
|
405
|
+
this.errorCallback = null;
|
|
406
|
+
}
|
|
407
|
+
installOnError() {
|
|
408
|
+
this.originalOnError = window.onerror;
|
|
409
|
+
window.onerror = (message, source, lineno, colno, error2) => {
|
|
410
|
+
if (this.originalOnError) {
|
|
411
|
+
this.originalOnError.call(window, message, source, lineno, colno, error2);
|
|
412
|
+
}
|
|
413
|
+
if (this.errorCallback) {
|
|
414
|
+
const errorObj = error2 || new Error(String(message));
|
|
415
|
+
if (!error2 && source) {
|
|
416
|
+
errorObj.filename = source;
|
|
417
|
+
errorObj.lineno = lineno;
|
|
418
|
+
errorObj.colno = colno;
|
|
419
|
+
}
|
|
420
|
+
this.errorCallback(errorObj, {
|
|
421
|
+
mechanism: { type: "onerror", handled: false },
|
|
422
|
+
source,
|
|
423
|
+
lineno,
|
|
424
|
+
colno
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
installOnUnhandledRejection() {
|
|
431
|
+
this.originalOnUnhandledRejection = this.handleUnhandledRejection.bind(this);
|
|
432
|
+
window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/integrations/console.ts
|
|
437
|
+
var ConsoleIntegration = class {
|
|
438
|
+
constructor() {
|
|
439
|
+
this.originalMethods = {};
|
|
440
|
+
this.callback = null;
|
|
441
|
+
this.levels = ["debug", "info", "warn", "error", "log"];
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Install console breadcrumb tracking
|
|
445
|
+
*/
|
|
446
|
+
install(callback, levels) {
|
|
447
|
+
this.callback = callback;
|
|
448
|
+
if (levels) {
|
|
449
|
+
this.levels = levels;
|
|
450
|
+
}
|
|
451
|
+
if (typeof console === "undefined") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
for (const level of this.levels) {
|
|
455
|
+
this.wrapConsoleMethod(level);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Uninstall console breadcrumb tracking
|
|
460
|
+
*/
|
|
461
|
+
uninstall() {
|
|
462
|
+
if (typeof console === "undefined") {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
for (const level of this.levels) {
|
|
466
|
+
if (this.originalMethods[level]) {
|
|
467
|
+
console[level] = this.originalMethods[level];
|
|
468
|
+
delete this.originalMethods[level];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
this.callback = null;
|
|
472
|
+
}
|
|
473
|
+
wrapConsoleMethod(level) {
|
|
474
|
+
const originalMethod = console[level];
|
|
475
|
+
if (!originalMethod) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
this.originalMethods[level] = originalMethod;
|
|
479
|
+
console[level] = (...args) => {
|
|
480
|
+
if (this.callback) {
|
|
481
|
+
this.callback({
|
|
482
|
+
category: "console",
|
|
483
|
+
message: this.formatArgs(args),
|
|
484
|
+
level: this.mapLevel(level),
|
|
485
|
+
data: args.length > 1 ? { arguments: args } : void 0
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
originalMethod.apply(console, args);
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
formatArgs(args) {
|
|
492
|
+
return args.map((arg) => {
|
|
493
|
+
if (typeof arg === "string") {
|
|
494
|
+
return arg;
|
|
495
|
+
}
|
|
496
|
+
if (arg instanceof Error) {
|
|
497
|
+
return arg.message;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
return JSON.stringify(arg);
|
|
501
|
+
} catch {
|
|
502
|
+
return String(arg);
|
|
503
|
+
}
|
|
504
|
+
}).join(" ");
|
|
505
|
+
}
|
|
506
|
+
mapLevel(consoleLevel) {
|
|
507
|
+
switch (consoleLevel) {
|
|
508
|
+
case "debug":
|
|
509
|
+
return "debug";
|
|
510
|
+
case "info":
|
|
511
|
+
case "log":
|
|
512
|
+
return "info";
|
|
513
|
+
case "warn":
|
|
514
|
+
return "warning";
|
|
515
|
+
case "error":
|
|
516
|
+
return "error";
|
|
517
|
+
default:
|
|
518
|
+
return "info";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/client.ts
|
|
524
|
+
init_telemetry();
|
|
525
|
+
var SDK_NAME = "@statly/observe-sdk";
|
|
526
|
+
var SDK_VERSION = "0.1.0";
|
|
527
|
+
var StatlyClient = class {
|
|
528
|
+
constructor(options) {
|
|
529
|
+
this.user = null;
|
|
530
|
+
this.initialized = false;
|
|
531
|
+
this.options = this.mergeOptions(options);
|
|
532
|
+
this.transport = new Transport({
|
|
533
|
+
dsn: this.options.dsn,
|
|
534
|
+
debug: this.options.debug
|
|
535
|
+
});
|
|
536
|
+
this.breadcrumbs = new BreadcrumbManager(this.options.maxBreadcrumbs);
|
|
537
|
+
this.globalHandlers = new GlobalHandlers();
|
|
538
|
+
this.consoleIntegration = new ConsoleIntegration();
|
|
539
|
+
TelemetryProvider.getInstance().setClient(this);
|
|
540
|
+
}
|
|
541
|
+
mergeOptions(options) {
|
|
542
|
+
return {
|
|
543
|
+
dsn: options.dsn,
|
|
544
|
+
release: options.release ?? "",
|
|
545
|
+
environment: options.environment ?? this.detectEnvironment(),
|
|
546
|
+
debug: options.debug ?? false,
|
|
547
|
+
sampleRate: options.sampleRate ?? 1,
|
|
548
|
+
maxBreadcrumbs: options.maxBreadcrumbs ?? 100,
|
|
549
|
+
autoCapture: options.autoCapture !== false,
|
|
550
|
+
captureConsole: options.captureConsole !== false,
|
|
551
|
+
captureNetwork: options.captureNetwork ?? false,
|
|
552
|
+
tags: options.tags ?? {},
|
|
553
|
+
beforeSend: options.beforeSend ?? ((e) => e)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
detectEnvironment() {
|
|
557
|
+
if (typeof window !== "undefined") {
|
|
558
|
+
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
|
559
|
+
return "development";
|
|
560
|
+
}
|
|
561
|
+
if (window.location.hostname.includes("staging") || window.location.hostname.includes("stage")) {
|
|
562
|
+
return "staging";
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return "production";
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Initialize the SDK
|
|
569
|
+
*/
|
|
570
|
+
init() {
|
|
571
|
+
if (this.initialized) {
|
|
572
|
+
if (this.options.debug) {
|
|
573
|
+
console.warn("[Statly] SDK already initialized");
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
this.initialized = true;
|
|
578
|
+
if (this.options.autoCapture) {
|
|
579
|
+
this.globalHandlers.install((error2, context) => {
|
|
580
|
+
this.captureError(error2, context);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
if (this.options.captureConsole) {
|
|
584
|
+
this.consoleIntegration.install((breadcrumb) => {
|
|
585
|
+
this.breadcrumbs.add(breadcrumb);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
this.addBreadcrumb({
|
|
589
|
+
category: "navigation",
|
|
590
|
+
message: "SDK initialized",
|
|
591
|
+
level: "info"
|
|
592
|
+
});
|
|
593
|
+
if (this.options.debug) {
|
|
594
|
+
console.log("[Statly] SDK initialized", {
|
|
595
|
+
environment: this.options.environment,
|
|
596
|
+
release: this.options.release
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Capture an exception/error
|
|
602
|
+
*/
|
|
603
|
+
captureException(error2, context) {
|
|
604
|
+
let errorObj;
|
|
605
|
+
if (error2 instanceof Error) {
|
|
606
|
+
errorObj = error2;
|
|
607
|
+
} else if (typeof error2 === "string") {
|
|
608
|
+
errorObj = new Error(error2);
|
|
609
|
+
} else {
|
|
610
|
+
errorObj = new Error("Unknown error");
|
|
611
|
+
errorObj.originalError = error2;
|
|
612
|
+
}
|
|
613
|
+
return this.captureError(errorObj, context);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Capture a message
|
|
617
|
+
*/
|
|
618
|
+
captureMessage(message, level = "info") {
|
|
619
|
+
const event = this.buildEvent({
|
|
620
|
+
message,
|
|
621
|
+
level
|
|
622
|
+
});
|
|
623
|
+
return this.sendEvent(event);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Capture a completed span
|
|
627
|
+
*/
|
|
628
|
+
captureSpan(span) {
|
|
629
|
+
const event = this.buildEvent({
|
|
630
|
+
message: `Span: ${span.name}`,
|
|
631
|
+
level: "span",
|
|
632
|
+
span: span.toDict()
|
|
633
|
+
});
|
|
634
|
+
return this.sendEvent(event);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Start a new tracing span
|
|
638
|
+
*/
|
|
639
|
+
startSpan(name, tags) {
|
|
640
|
+
return TelemetryProvider.getInstance().startSpan(name, tags);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Execute a function within a trace span
|
|
644
|
+
*/
|
|
645
|
+
async trace(name, operation, tags) {
|
|
646
|
+
const { trace: traceFn } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
|
|
647
|
+
return traceFn(name, operation, tags);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Internal method to capture an error
|
|
651
|
+
*/
|
|
652
|
+
captureError(error2, context) {
|
|
653
|
+
if (Math.random() > this.options.sampleRate) {
|
|
654
|
+
return "";
|
|
655
|
+
}
|
|
656
|
+
const event = this.buildEvent({
|
|
657
|
+
message: error2.message,
|
|
658
|
+
level: "error",
|
|
659
|
+
stack: error2.stack,
|
|
660
|
+
exception: {
|
|
661
|
+
type: error2.name,
|
|
662
|
+
value: error2.message,
|
|
663
|
+
stacktrace: this.parseStackTrace(error2.stack)
|
|
664
|
+
},
|
|
665
|
+
extra: context
|
|
666
|
+
});
|
|
667
|
+
return this.sendEvent(event);
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Build a complete event from partial data
|
|
671
|
+
*/
|
|
672
|
+
buildEvent(partial) {
|
|
673
|
+
const event = {
|
|
674
|
+
message: partial.message || "Unknown error",
|
|
675
|
+
timestamp: Date.now(),
|
|
676
|
+
level: partial.level || "error",
|
|
677
|
+
environment: this.options.environment,
|
|
678
|
+
release: this.options.release || void 0,
|
|
679
|
+
url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
680
|
+
user: this.user || void 0,
|
|
681
|
+
tags: { ...this.options.tags, ...partial.tags },
|
|
682
|
+
extra: partial.extra,
|
|
683
|
+
breadcrumbs: this.breadcrumbs.getAll(),
|
|
684
|
+
browser: this.getBrowserInfo(),
|
|
685
|
+
os: this.getOSInfo(),
|
|
686
|
+
sdk: {
|
|
687
|
+
name: SDK_NAME,
|
|
688
|
+
version: SDK_VERSION
|
|
689
|
+
},
|
|
690
|
+
...partial
|
|
691
|
+
};
|
|
692
|
+
return event;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Parse a stack trace string into structured frames
|
|
696
|
+
*/
|
|
697
|
+
parseStackTrace(stack) {
|
|
698
|
+
if (!stack) {
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
const frames = [];
|
|
702
|
+
const lines = stack.split("\n");
|
|
703
|
+
for (const line of lines) {
|
|
704
|
+
const chromeMatch = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
705
|
+
if (chromeMatch) {
|
|
706
|
+
frames.push({
|
|
707
|
+
function: chromeMatch[1] || "<anonymous>",
|
|
708
|
+
filename: chromeMatch[2],
|
|
709
|
+
lineno: parseInt(chromeMatch[3], 10),
|
|
710
|
+
colno: parseInt(chromeMatch[4], 10)
|
|
711
|
+
});
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
const firefoxMatch = line.match(/^(.+?)@(.+?):(\d+):(\d+)$/);
|
|
715
|
+
if (firefoxMatch) {
|
|
716
|
+
frames.push({
|
|
717
|
+
function: firefoxMatch[1] || "<anonymous>",
|
|
718
|
+
filename: firefoxMatch[2],
|
|
719
|
+
lineno: parseInt(firefoxMatch[3], 10),
|
|
720
|
+
colno: parseInt(firefoxMatch[4], 10)
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return frames.length > 0 ? { frames } : void 0;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Send an event to the server
|
|
728
|
+
*/
|
|
729
|
+
sendEvent(event) {
|
|
730
|
+
const processed = this.options.beforeSend(event);
|
|
731
|
+
if (!processed) {
|
|
732
|
+
if (this.options.debug) {
|
|
733
|
+
console.log("[Statly] Event dropped by beforeSend");
|
|
734
|
+
}
|
|
735
|
+
return "";
|
|
736
|
+
}
|
|
737
|
+
const eventId = this.generateEventId();
|
|
738
|
+
this.breadcrumbs.add({
|
|
739
|
+
category: "statly",
|
|
740
|
+
message: `Captured ${event.level}: ${event.message.slice(0, 50)}`,
|
|
741
|
+
level: "info"
|
|
742
|
+
});
|
|
743
|
+
this.transport.enqueue(processed);
|
|
744
|
+
if (this.options.debug) {
|
|
745
|
+
console.log("[Statly] Event captured:", eventId, event.message);
|
|
746
|
+
}
|
|
747
|
+
return eventId;
|
|
748
|
+
}
|
|
749
|
+
generateEventId() {
|
|
750
|
+
return crypto.randomUUID?.() || "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
751
|
+
const r = Math.random() * 16 | 0;
|
|
752
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
753
|
+
return v.toString(16);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Set user context
|
|
758
|
+
*/
|
|
759
|
+
setUser(user) {
|
|
760
|
+
this.user = user;
|
|
761
|
+
if (this.options.debug && user) {
|
|
762
|
+
console.log("[Statly] User set:", user.id || user.email);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Set a single tag
|
|
767
|
+
*/
|
|
768
|
+
setTag(key, value) {
|
|
769
|
+
this.options.tags[key] = value;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Set multiple tags
|
|
773
|
+
*/
|
|
774
|
+
setTags(tags) {
|
|
775
|
+
Object.assign(this.options.tags, tags);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Add a breadcrumb
|
|
779
|
+
*/
|
|
780
|
+
addBreadcrumb(breadcrumb) {
|
|
781
|
+
this.breadcrumbs.add(breadcrumb);
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Get browser info
|
|
785
|
+
*/
|
|
786
|
+
getBrowserInfo() {
|
|
787
|
+
if (typeof navigator === "undefined") {
|
|
788
|
+
return void 0;
|
|
789
|
+
}
|
|
790
|
+
const ua = navigator.userAgent;
|
|
791
|
+
let name = "Unknown";
|
|
792
|
+
let version = "";
|
|
793
|
+
if (ua.includes("Firefox/")) {
|
|
794
|
+
name = "Firefox";
|
|
795
|
+
version = ua.split("Firefox/")[1]?.split(" ")[0] || "";
|
|
796
|
+
} else if (ua.includes("Chrome/")) {
|
|
797
|
+
name = "Chrome";
|
|
798
|
+
version = ua.split("Chrome/")[1]?.split(" ")[0] || "";
|
|
799
|
+
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
800
|
+
name = "Safari";
|
|
801
|
+
version = ua.split("Version/")[1]?.split(" ")[0] || "";
|
|
802
|
+
} else if (ua.includes("Edge/") || ua.includes("Edg/")) {
|
|
803
|
+
name = "Edge";
|
|
804
|
+
version = ua.split(/Edg?e?\//)[1]?.split(" ")[0] || "";
|
|
805
|
+
}
|
|
806
|
+
return { name, version };
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Get OS info
|
|
810
|
+
*/
|
|
811
|
+
getOSInfo() {
|
|
812
|
+
if (typeof navigator === "undefined") {
|
|
813
|
+
return void 0;
|
|
814
|
+
}
|
|
815
|
+
const ua = navigator.userAgent;
|
|
816
|
+
let name = "Unknown";
|
|
817
|
+
let version = "";
|
|
818
|
+
if (ua.includes("Windows")) {
|
|
819
|
+
name = "Windows";
|
|
820
|
+
const match = ua.match(/Windows NT (\d+\.\d+)/);
|
|
821
|
+
if (match) version = match[1];
|
|
822
|
+
} else if (ua.includes("Mac OS X")) {
|
|
823
|
+
name = "macOS";
|
|
824
|
+
const match = ua.match(/Mac OS X (\d+[._]\d+)/);
|
|
825
|
+
if (match) version = match[1].replace("_", ".");
|
|
826
|
+
} else if (ua.includes("Linux")) {
|
|
827
|
+
name = "Linux";
|
|
828
|
+
} else if (ua.includes("Android")) {
|
|
829
|
+
name = "Android";
|
|
830
|
+
const match = ua.match(/Android (\d+\.\d+)/);
|
|
831
|
+
if (match) version = match[1];
|
|
832
|
+
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
833
|
+
name = "iOS";
|
|
834
|
+
const match = ua.match(/OS (\d+_\d+)/);
|
|
835
|
+
if (match) version = match[1].replace("_", ".");
|
|
836
|
+
}
|
|
837
|
+
return { name, version };
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Flush pending events and clean up
|
|
841
|
+
*/
|
|
842
|
+
async close() {
|
|
843
|
+
this.globalHandlers.uninstall();
|
|
844
|
+
this.consoleIntegration.uninstall();
|
|
845
|
+
await this.transport.flush();
|
|
846
|
+
this.transport.destroy();
|
|
847
|
+
this.initialized = false;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Force flush pending events
|
|
851
|
+
*/
|
|
852
|
+
async flush() {
|
|
853
|
+
await this.transport.flush();
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/logger/formatters/console.ts
|
|
858
|
+
var COLORS = {
|
|
859
|
+
reset: "\x1B[0m",
|
|
860
|
+
bold: "\x1B[1m",
|
|
861
|
+
dim: "\x1B[2m",
|
|
862
|
+
// Foreground colors
|
|
863
|
+
black: "\x1B[30m",
|
|
864
|
+
red: "\x1B[31m",
|
|
865
|
+
green: "\x1B[32m",
|
|
866
|
+
yellow: "\x1B[33m",
|
|
867
|
+
blue: "\x1B[34m",
|
|
868
|
+
magenta: "\x1B[35m",
|
|
869
|
+
cyan: "\x1B[36m",
|
|
870
|
+
white: "\x1B[37m",
|
|
871
|
+
gray: "\x1B[90m",
|
|
872
|
+
// Background colors
|
|
873
|
+
bgRed: "\x1B[41m",
|
|
874
|
+
bgYellow: "\x1B[43m"
|
|
875
|
+
};
|
|
876
|
+
var LEVEL_COLORS = {
|
|
877
|
+
trace: COLORS.gray,
|
|
878
|
+
debug: COLORS.cyan,
|
|
879
|
+
info: COLORS.green,
|
|
880
|
+
warn: COLORS.yellow,
|
|
881
|
+
error: COLORS.red,
|
|
882
|
+
fatal: `${COLORS.bgRed}${COLORS.white}`,
|
|
883
|
+
audit: COLORS.magenta
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// src/index.ts
|
|
887
|
+
var client = null;
|
|
888
|
+
function loadDsnFromEnv() {
|
|
889
|
+
if (typeof process !== "undefined" && process.env) {
|
|
890
|
+
return process.env.STATLY_DSN || process.env.NEXT_PUBLIC_STATLY_DSN || process.env.STATLY_OBSERVE_DSN;
|
|
891
|
+
}
|
|
892
|
+
return void 0;
|
|
893
|
+
}
|
|
894
|
+
function loadEnvironmentFromEnv() {
|
|
895
|
+
if (typeof process !== "undefined" && process.env) {
|
|
896
|
+
return process.env.STATLY_ENVIRONMENT || process.env.NODE_ENV;
|
|
897
|
+
}
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
function init(options) {
|
|
901
|
+
if (client) {
|
|
902
|
+
console.warn("[Statly] SDK already initialized. Call close() first to reinitialize.");
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const dsn = options?.dsn || loadDsnFromEnv();
|
|
906
|
+
if (!dsn) {
|
|
907
|
+
console.error("[Statly] No DSN provided. Set STATLY_DSN in your environment or pass dsn to init().");
|
|
908
|
+
console.error("[Statly] Get your DSN at https://statly.live/dashboard/observe/setup");
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const environment = options?.environment || loadEnvironmentFromEnv();
|
|
912
|
+
const finalOptions = {
|
|
913
|
+
...options,
|
|
914
|
+
dsn,
|
|
915
|
+
environment
|
|
916
|
+
};
|
|
917
|
+
client = new StatlyClient(finalOptions);
|
|
918
|
+
client.init();
|
|
919
|
+
}
|
|
920
|
+
function captureException(error2, context) {
|
|
921
|
+
if (!client) {
|
|
922
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
923
|
+
return "";
|
|
924
|
+
}
|
|
925
|
+
return client.captureException(error2, context);
|
|
926
|
+
}
|
|
927
|
+
function captureMessage(message, level = "info") {
|
|
928
|
+
if (!client) {
|
|
929
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
930
|
+
return "";
|
|
931
|
+
}
|
|
932
|
+
return client.captureMessage(message, level);
|
|
933
|
+
}
|
|
934
|
+
function setUser(user) {
|
|
935
|
+
if (!client) {
|
|
936
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
client.setUser(user);
|
|
940
|
+
}
|
|
941
|
+
function setTag(key, value) {
|
|
942
|
+
if (!client) {
|
|
943
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
client.setTag(key, value);
|
|
947
|
+
}
|
|
948
|
+
function setTags(tags) {
|
|
949
|
+
if (!client) {
|
|
950
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
client.setTags(tags);
|
|
954
|
+
}
|
|
955
|
+
function addBreadcrumb(breadcrumb) {
|
|
956
|
+
if (!client) {
|
|
957
|
+
console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
client.addBreadcrumb(breadcrumb);
|
|
961
|
+
}
|
|
962
|
+
async function flush() {
|
|
963
|
+
if (!client) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
await client.flush();
|
|
967
|
+
}
|
|
968
|
+
async function close() {
|
|
969
|
+
if (!client) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
await client.close();
|
|
973
|
+
client = null;
|
|
974
|
+
}
|
|
975
|
+
function getClient() {
|
|
976
|
+
return client;
|
|
977
|
+
}
|
|
978
|
+
async function trace3(name, operation, tags) {
|
|
979
|
+
if (!client) {
|
|
980
|
+
return operation(null);
|
|
981
|
+
}
|
|
982
|
+
return client.trace(name, operation, tags);
|
|
983
|
+
}
|
|
984
|
+
function startSpan(name, tags) {
|
|
985
|
+
if (!client) return null;
|
|
986
|
+
return client.startSpan(name, tags);
|
|
987
|
+
}
|
|
988
|
+
function captureSpan(span) {
|
|
989
|
+
if (!client) return "";
|
|
990
|
+
return client.captureSpan(span);
|
|
991
|
+
}
|
|
992
|
+
var Statly = {
|
|
993
|
+
init,
|
|
994
|
+
captureException,
|
|
995
|
+
captureMessage,
|
|
996
|
+
setUser,
|
|
997
|
+
setTag,
|
|
998
|
+
setTags,
|
|
999
|
+
addBreadcrumb,
|
|
1000
|
+
flush,
|
|
1001
|
+
close,
|
|
1002
|
+
getClient,
|
|
1003
|
+
trace: trace3,
|
|
1004
|
+
startSpan,
|
|
1005
|
+
captureSpan
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// src/integrations/fastify.ts
|
|
1009
|
+
function statlyFastifyPlugin(fastify, options, done) {
|
|
1010
|
+
const {
|
|
1011
|
+
captureValidationErrors = true,
|
|
1012
|
+
shouldCapture,
|
|
1013
|
+
skipStatusCodes = [400, 401, 403, 404]
|
|
1014
|
+
} = options;
|
|
1015
|
+
fastify.addHook("onRequest", (request, _reply, hookDone) => {
|
|
1016
|
+
request.statlyStartTime = Date.now();
|
|
1017
|
+
Statly.addBreadcrumb({
|
|
1018
|
+
category: "http",
|
|
1019
|
+
message: `${request.method} ${request.routerPath || request.url}`,
|
|
1020
|
+
level: "info",
|
|
1021
|
+
data: {
|
|
1022
|
+
method: request.method,
|
|
1023
|
+
url: request.url,
|
|
1024
|
+
routerPath: request.routerPath,
|
|
1025
|
+
requestId: request.id
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
hookDone();
|
|
1029
|
+
});
|
|
1030
|
+
fastify.addHook("onResponse", (request, reply, hookDone) => {
|
|
1031
|
+
const startTime = request.statlyStartTime;
|
|
1032
|
+
const duration = startTime ? Date.now() - startTime : void 0;
|
|
1033
|
+
Statly.addBreadcrumb({
|
|
1034
|
+
category: "http",
|
|
1035
|
+
message: `Response ${reply.statusCode}`,
|
|
1036
|
+
level: reply.statusCode >= 400 ? "error" : "info",
|
|
1037
|
+
data: {
|
|
1038
|
+
statusCode: reply.statusCode,
|
|
1039
|
+
duration,
|
|
1040
|
+
requestId: request.id
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
hookDone();
|
|
1044
|
+
});
|
|
1045
|
+
fastify.setErrorHandler((error2, request, reply) => {
|
|
1046
|
+
const statusCode = error2.statusCode || 500;
|
|
1047
|
+
if (skipStatusCodes.includes(statusCode)) {
|
|
1048
|
+
throw error2;
|
|
1049
|
+
}
|
|
1050
|
+
if (!captureValidationErrors && error2.validation) {
|
|
1051
|
+
throw error2;
|
|
1052
|
+
}
|
|
1053
|
+
if (shouldCapture && !shouldCapture(error2)) {
|
|
1054
|
+
throw error2;
|
|
1055
|
+
}
|
|
1056
|
+
const context = {
|
|
1057
|
+
request: {
|
|
1058
|
+
id: request.id,
|
|
1059
|
+
method: request.method,
|
|
1060
|
+
url: request.url,
|
|
1061
|
+
routerPath: request.routerPath,
|
|
1062
|
+
headers: sanitizeHeaders(request.headers),
|
|
1063
|
+
query: request.query,
|
|
1064
|
+
params: request.params
|
|
1065
|
+
},
|
|
1066
|
+
error: {
|
|
1067
|
+
statusCode: error2.statusCode,
|
|
1068
|
+
code: error2.code
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
if (request.ip) {
|
|
1072
|
+
context.ip = request.ip;
|
|
1073
|
+
}
|
|
1074
|
+
if (error2.validation) {
|
|
1075
|
+
context.validation = error2.validation;
|
|
1076
|
+
}
|
|
1077
|
+
Statly.setTag("http.method", request.method);
|
|
1078
|
+
Statly.setTag("http.url", request.routerPath || request.url);
|
|
1079
|
+
Statly.setTag("http.status_code", String(statusCode));
|
|
1080
|
+
Statly.captureException(error2, context);
|
|
1081
|
+
throw error2;
|
|
1082
|
+
});
|
|
1083
|
+
done();
|
|
1084
|
+
}
|
|
1085
|
+
var statlyPlugin = statlyFastifyPlugin;
|
|
1086
|
+
function createRequestCapture(request) {
|
|
1087
|
+
return (error2, additionalContext) => {
|
|
1088
|
+
const context = {
|
|
1089
|
+
request: {
|
|
1090
|
+
id: request.id,
|
|
1091
|
+
method: request.method,
|
|
1092
|
+
url: request.url,
|
|
1093
|
+
routerPath: request.routerPath
|
|
1094
|
+
},
|
|
1095
|
+
...additionalContext
|
|
1096
|
+
};
|
|
1097
|
+
return Statly.captureException(error2, context);
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function sanitizeHeaders(headers) {
|
|
1101
|
+
const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
|
|
1102
|
+
const sanitized = {};
|
|
1103
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1104
|
+
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
|
1105
|
+
sanitized[key] = "[Filtered]";
|
|
1106
|
+
} else {
|
|
1107
|
+
sanitized[key] = value;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return sanitized;
|
|
1111
|
+
}
|
|
1112
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1113
|
+
0 && (module.exports = {
|
|
1114
|
+
createRequestCapture,
|
|
1115
|
+
statlyFastifyPlugin,
|
|
1116
|
+
statlyPlugin
|
|
1117
|
+
});
|