@error-explorer/node 1.1.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/index.cjs +1720 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +1674 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.cjs +1625 -0
- package/dist/middleware/express.cjs.map +1 -0
- package/dist/middleware/express.d.cts +60 -0
- package/dist/middleware/express.d.ts +60 -0
- package/dist/middleware/express.js +1597 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/http.cjs +1587 -0
- package/dist/middleware/http.cjs.map +1 -0
- package/dist/middleware/http.d.cts +29 -0
- package/dist/middleware/http.d.ts +29 -0
- package/dist/middleware/http.js +1560 -0
- package/dist/middleware/http.js.map +1 -0
- package/dist/types-D5NSSblm.d.cts +255 -0
- package/dist/types-D5NSSblm.d.ts +255 -0
- package/package.json +78 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1720 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var os2 = require('os');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var http = require('http');
|
|
9
|
+
var https = require('https');
|
|
10
|
+
|
|
11
|
+
function _interopNamespace(e) {
|
|
12
|
+
if (e && e.__esModule) return e;
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n.default = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var os2__namespace = /*#__PURE__*/_interopNamespace(os2);
|
|
30
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
31
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
32
|
+
var http__namespace = /*#__PURE__*/_interopNamespace(http);
|
|
33
|
+
var https__namespace = /*#__PURE__*/_interopNamespace(https);
|
|
34
|
+
|
|
35
|
+
// src/config/Config.ts
|
|
36
|
+
var DEFAULT_ENDPOINT = "https://error-explorer.com/api/v1/webhook";
|
|
37
|
+
function parseDsn(dsn) {
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(dsn);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(`Invalid DSN format: ${dsn}. Expected format: https://token@host/path`);
|
|
43
|
+
}
|
|
44
|
+
const token = url.username;
|
|
45
|
+
if (!token) {
|
|
46
|
+
throw new Error("DSN must contain a token (username part)");
|
|
47
|
+
}
|
|
48
|
+
url.username = "";
|
|
49
|
+
url.password = "";
|
|
50
|
+
return {
|
|
51
|
+
token,
|
|
52
|
+
endpoint: url.toString().replace(/\/$/, "")
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function detectEnvironment() {
|
|
56
|
+
return process.env["NODE_ENV"] || "development";
|
|
57
|
+
}
|
|
58
|
+
function getDefaultServerName() {
|
|
59
|
+
try {
|
|
60
|
+
return os2__namespace.hostname();
|
|
61
|
+
} catch {
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function resolveConfig(options) {
|
|
66
|
+
let token;
|
|
67
|
+
let endpoint;
|
|
68
|
+
if (options.dsn) {
|
|
69
|
+
const parsed = parseDsn(options.dsn);
|
|
70
|
+
token = parsed.token;
|
|
71
|
+
endpoint = options.endpoint || parsed.endpoint;
|
|
72
|
+
} else if (options.token) {
|
|
73
|
+
token = options.token;
|
|
74
|
+
endpoint = options.endpoint || DEFAULT_ENDPOINT;
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error("Either token or dsn must be provided");
|
|
77
|
+
}
|
|
78
|
+
if (!token.startsWith("ee_")) {
|
|
79
|
+
console.warn('[ErrorExplorer] Token should start with "ee_" prefix');
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
token,
|
|
83
|
+
endpoint,
|
|
84
|
+
environment: options.environment || detectEnvironment(),
|
|
85
|
+
release: options.release || "",
|
|
86
|
+
serverName: options.serverName || getDefaultServerName(),
|
|
87
|
+
autoCapture: {
|
|
88
|
+
uncaughtExceptions: options.autoCapture?.uncaughtExceptions ?? true,
|
|
89
|
+
unhandledRejections: options.autoCapture?.unhandledRejections ?? true,
|
|
90
|
+
console: options.autoCapture?.console ?? false
|
|
91
|
+
},
|
|
92
|
+
breadcrumbs: {
|
|
93
|
+
enabled: options.breadcrumbs?.enabled ?? true,
|
|
94
|
+
maxBreadcrumbs: options.breadcrumbs?.maxBreadcrumbs ?? 50,
|
|
95
|
+
http: options.breadcrumbs?.http ?? true,
|
|
96
|
+
console: options.breadcrumbs?.console ?? true
|
|
97
|
+
},
|
|
98
|
+
beforeSend: options.beforeSend,
|
|
99
|
+
ignoreErrors: options.ignoreErrors || [],
|
|
100
|
+
maxRetries: options.maxRetries ?? 3,
|
|
101
|
+
timeout: options.timeout ?? 5e3,
|
|
102
|
+
debug: options.debug ?? false,
|
|
103
|
+
hmacSecret: options.hmacSecret,
|
|
104
|
+
exitOnUncaughtException: options.exitOnUncaughtException ?? true
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function matchesPattern(str, patterns) {
|
|
108
|
+
return patterns.some((pattern) => {
|
|
109
|
+
if (typeof pattern === "string") {
|
|
110
|
+
return str.includes(pattern);
|
|
111
|
+
}
|
|
112
|
+
return pattern.test(str);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function parseStackTrace(stack) {
|
|
116
|
+
if (!stack) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const frames = [];
|
|
120
|
+
const lines = stack.split("\n");
|
|
121
|
+
for (const line of lines) {
|
|
122
|
+
const frame = parseStackLine(line);
|
|
123
|
+
if (frame) {
|
|
124
|
+
frames.push(frame);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return frames;
|
|
128
|
+
}
|
|
129
|
+
function parseStackLine(line) {
|
|
130
|
+
if (!line.trim().startsWith("at ")) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const content = line.replace(/^\s*at\s+/, "").trim();
|
|
134
|
+
const withFunctionMatch = content.match(
|
|
135
|
+
/^(?:async\s+)?(.+?)\s+\((.+?):(\d+):(\d+)\)$/
|
|
136
|
+
);
|
|
137
|
+
if (withFunctionMatch) {
|
|
138
|
+
const [, fn, file, lineNo, colNo] = withFunctionMatch;
|
|
139
|
+
return createFrame(fn, file, lineNo, colNo);
|
|
140
|
+
}
|
|
141
|
+
const anonymousMatch = content.match(/^(.+?):(\d+):(\d+)$/);
|
|
142
|
+
if (anonymousMatch) {
|
|
143
|
+
const [, file, lineNo, colNo] = anonymousMatch;
|
|
144
|
+
return createFrame(void 0, file, lineNo, colNo);
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function createFrame(fn, file, lineNo, colNo) {
|
|
149
|
+
const frame = {};
|
|
150
|
+
if (fn) {
|
|
151
|
+
frame.function = cleanFunctionName(fn);
|
|
152
|
+
}
|
|
153
|
+
if (file) {
|
|
154
|
+
frame.filename = file;
|
|
155
|
+
frame.abs_path = path__namespace.isAbsolute(file) ? file : void 0;
|
|
156
|
+
frame.in_app = !file.includes("node_modules") && !file.startsWith("node:");
|
|
157
|
+
if (frame.in_app && file.includes("/")) {
|
|
158
|
+
const parts = file.split("/");
|
|
159
|
+
frame.module = parts[parts.length - 1]?.replace(/\.[^/.]+$/, "");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (lineNo) {
|
|
163
|
+
frame.lineno = parseInt(lineNo, 10);
|
|
164
|
+
}
|
|
165
|
+
if (colNo) {
|
|
166
|
+
frame.colno = parseInt(colNo, 10);
|
|
167
|
+
}
|
|
168
|
+
return frame;
|
|
169
|
+
}
|
|
170
|
+
function cleanFunctionName(name) {
|
|
171
|
+
name = name.replace(/^Object\./, "");
|
|
172
|
+
name = name.replace(/^Module\./, "");
|
|
173
|
+
if (name === "<anonymous>") {
|
|
174
|
+
return "(anonymous)";
|
|
175
|
+
}
|
|
176
|
+
return name;
|
|
177
|
+
}
|
|
178
|
+
function getErrorName(error) {
|
|
179
|
+
if (error instanceof Error) {
|
|
180
|
+
return error.name || error.constructor.name;
|
|
181
|
+
}
|
|
182
|
+
if (typeof error === "object" && error !== null) {
|
|
183
|
+
const obj = error;
|
|
184
|
+
if (typeof obj["name"] === "string") {
|
|
185
|
+
return obj["name"];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return "Error";
|
|
189
|
+
}
|
|
190
|
+
function getErrorMessage(error) {
|
|
191
|
+
if (error instanceof Error) {
|
|
192
|
+
return error.message;
|
|
193
|
+
}
|
|
194
|
+
if (typeof error === "string") {
|
|
195
|
+
return error;
|
|
196
|
+
}
|
|
197
|
+
if (typeof error === "object" && error !== null) {
|
|
198
|
+
const obj = error;
|
|
199
|
+
if (typeof obj["message"] === "string") {
|
|
200
|
+
return obj["message"];
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
return JSON.stringify(error);
|
|
204
|
+
} catch {
|
|
205
|
+
return String(error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return String(error);
|
|
209
|
+
}
|
|
210
|
+
function getErrorStack(error) {
|
|
211
|
+
if (error instanceof Error) {
|
|
212
|
+
return error.stack;
|
|
213
|
+
}
|
|
214
|
+
if (typeof error === "object" && error !== null) {
|
|
215
|
+
const obj = error;
|
|
216
|
+
if (typeof obj["stack"] === "string") {
|
|
217
|
+
return obj["stack"];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
function generateUuid() {
|
|
223
|
+
return crypto__namespace.randomUUID();
|
|
224
|
+
}
|
|
225
|
+
function generateShortId() {
|
|
226
|
+
return crypto__namespace.randomBytes(4).toString("hex");
|
|
227
|
+
}
|
|
228
|
+
function generateTransactionId() {
|
|
229
|
+
return `txn_${crypto__namespace.randomBytes(12).toString("hex")}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/context/ProcessContext.ts
|
|
233
|
+
function collectProcessContext() {
|
|
234
|
+
const memoryUsage = process.memoryUsage();
|
|
235
|
+
const cpuUsage = process.cpuUsage();
|
|
236
|
+
return {
|
|
237
|
+
pid: process.pid,
|
|
238
|
+
ppid: process.ppid,
|
|
239
|
+
uptime: process.uptime(),
|
|
240
|
+
memory: {
|
|
241
|
+
rss: memoryUsage.rss,
|
|
242
|
+
heapTotal: memoryUsage.heapTotal,
|
|
243
|
+
heapUsed: memoryUsage.heapUsed,
|
|
244
|
+
external: memoryUsage.external,
|
|
245
|
+
arrayBuffers: memoryUsage.arrayBuffers
|
|
246
|
+
},
|
|
247
|
+
cpu: {
|
|
248
|
+
user: cpuUsage.user,
|
|
249
|
+
system: cpuUsage.system
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
var cachedOsContext = null;
|
|
254
|
+
function collectOsContext() {
|
|
255
|
+
if (cachedOsContext) {
|
|
256
|
+
return cachedOsContext;
|
|
257
|
+
}
|
|
258
|
+
cachedOsContext = {
|
|
259
|
+
name: os2__namespace.platform(),
|
|
260
|
+
version: os2__namespace.release(),
|
|
261
|
+
arch: os2__namespace.arch(),
|
|
262
|
+
kernel_version: os2__namespace.version()
|
|
263
|
+
};
|
|
264
|
+
return cachedOsContext;
|
|
265
|
+
}
|
|
266
|
+
function resetOsContext() {
|
|
267
|
+
cachedOsContext = null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/context/RuntimeContext.ts
|
|
271
|
+
var cachedRuntimeContext = null;
|
|
272
|
+
function collectRuntimeContext() {
|
|
273
|
+
if (cachedRuntimeContext) {
|
|
274
|
+
return cachedRuntimeContext;
|
|
275
|
+
}
|
|
276
|
+
cachedRuntimeContext = {
|
|
277
|
+
name: "node",
|
|
278
|
+
version: process.version
|
|
279
|
+
};
|
|
280
|
+
return cachedRuntimeContext;
|
|
281
|
+
}
|
|
282
|
+
function resetRuntimeContext() {
|
|
283
|
+
cachedRuntimeContext = null;
|
|
284
|
+
}
|
|
285
|
+
var serverContext = {
|
|
286
|
+
name: void 0,
|
|
287
|
+
hostname: os2__namespace.hostname()
|
|
288
|
+
};
|
|
289
|
+
function getServerContext() {
|
|
290
|
+
return serverContext;
|
|
291
|
+
}
|
|
292
|
+
function setServerName(name) {
|
|
293
|
+
serverContext = {
|
|
294
|
+
...serverContext,
|
|
295
|
+
name
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function resetServerContext() {
|
|
299
|
+
serverContext = {
|
|
300
|
+
name: void 0,
|
|
301
|
+
hostname: os2__namespace.hostname()
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/context/UserContext.ts
|
|
306
|
+
var UserContextManager = class {
|
|
307
|
+
user = null;
|
|
308
|
+
/**
|
|
309
|
+
* Set user context
|
|
310
|
+
*/
|
|
311
|
+
setUser(user) {
|
|
312
|
+
this.user = user;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get current user context
|
|
316
|
+
*/
|
|
317
|
+
getUser() {
|
|
318
|
+
return this.user;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Clear user context
|
|
322
|
+
*/
|
|
323
|
+
clearUser() {
|
|
324
|
+
this.user = null;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Reset manager state
|
|
328
|
+
*/
|
|
329
|
+
reset() {
|
|
330
|
+
this.user = null;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
var userContextManager = null;
|
|
334
|
+
function getUserContextManager() {
|
|
335
|
+
if (!userContextManager) {
|
|
336
|
+
userContextManager = new UserContextManager();
|
|
337
|
+
}
|
|
338
|
+
return userContextManager;
|
|
339
|
+
}
|
|
340
|
+
function resetUserContextManager() {
|
|
341
|
+
if (userContextManager) {
|
|
342
|
+
userContextManager.reset();
|
|
343
|
+
}
|
|
344
|
+
userContextManager = null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/context/RequestContext.ts
|
|
348
|
+
function extractRequestContext(req) {
|
|
349
|
+
const headers = {};
|
|
350
|
+
const sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"];
|
|
351
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
352
|
+
const lowerKey = key.toLowerCase();
|
|
353
|
+
if (!sensitiveHeaders.includes(lowerKey)) {
|
|
354
|
+
headers[key] = value;
|
|
355
|
+
} else {
|
|
356
|
+
headers[key] = "[Filtered]";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const context = {
|
|
360
|
+
method: req.method,
|
|
361
|
+
headers,
|
|
362
|
+
url: getFullUrl(req)
|
|
363
|
+
};
|
|
364
|
+
const url = req.url || "";
|
|
365
|
+
const queryIndex = url.indexOf("?");
|
|
366
|
+
if (queryIndex !== -1) {
|
|
367
|
+
context.query_string = url.substring(queryIndex + 1);
|
|
368
|
+
}
|
|
369
|
+
const expressReq = req;
|
|
370
|
+
if (expressReq.body !== void 0) {
|
|
371
|
+
context.data = sanitizeBody(expressReq.body);
|
|
372
|
+
}
|
|
373
|
+
if (expressReq.cookies) {
|
|
374
|
+
context.cookies = sanitizeCookies(expressReq.cookies);
|
|
375
|
+
}
|
|
376
|
+
return context;
|
|
377
|
+
}
|
|
378
|
+
function getFullUrl(req) {
|
|
379
|
+
const expressReq = req;
|
|
380
|
+
const path2 = expressReq.originalUrl || req.url || "/";
|
|
381
|
+
const protocol = req.headers["x-forwarded-proto"] || "http";
|
|
382
|
+
const host = req.headers.host || "localhost";
|
|
383
|
+
return `${protocol}://${host}${path2}`;
|
|
384
|
+
}
|
|
385
|
+
function sanitizeBody(body) {
|
|
386
|
+
if (!body || typeof body !== "object") {
|
|
387
|
+
return body;
|
|
388
|
+
}
|
|
389
|
+
const sensitiveKeys = ["password", "secret", "token", "api_key", "apiKey", "credit_card", "creditCard", "cvv", "ssn"];
|
|
390
|
+
const sanitized = {};
|
|
391
|
+
for (const [key, value] of Object.entries(body)) {
|
|
392
|
+
const lowerKey = key.toLowerCase();
|
|
393
|
+
if (sensitiveKeys.some((sk) => lowerKey.includes(sk))) {
|
|
394
|
+
sanitized[key] = "[Filtered]";
|
|
395
|
+
} else if (typeof value === "object" && value !== null) {
|
|
396
|
+
sanitized[key] = sanitizeBody(value);
|
|
397
|
+
} else {
|
|
398
|
+
sanitized[key] = value;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return sanitized;
|
|
402
|
+
}
|
|
403
|
+
function sanitizeCookies(cookies) {
|
|
404
|
+
const sensitiveNames = ["session", "sess", "token", "auth", "jwt", "sid"];
|
|
405
|
+
const sanitized = {};
|
|
406
|
+
for (const [key, value] of Object.entries(cookies)) {
|
|
407
|
+
const lowerKey = key.toLowerCase();
|
|
408
|
+
if (sensitiveNames.some((sn) => lowerKey.includes(sn))) {
|
|
409
|
+
sanitized[key] = "[Filtered]";
|
|
410
|
+
} else {
|
|
411
|
+
sanitized[key] = value;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return sanitized;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/breadcrumbs/BreadcrumbManager.ts
|
|
418
|
+
var BreadcrumbManager = class {
|
|
419
|
+
breadcrumbs = [];
|
|
420
|
+
maxBreadcrumbs = 50;
|
|
421
|
+
/**
|
|
422
|
+
* Initialize with configuration
|
|
423
|
+
*/
|
|
424
|
+
init(config) {
|
|
425
|
+
this.maxBreadcrumbs = config.breadcrumbs.maxBreadcrumbs;
|
|
426
|
+
this.breadcrumbs = [];
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Add a breadcrumb
|
|
430
|
+
*/
|
|
431
|
+
add(breadcrumb) {
|
|
432
|
+
const internal = {
|
|
433
|
+
...breadcrumb,
|
|
434
|
+
timestamp: breadcrumb.timestamp ?? Date.now()
|
|
435
|
+
};
|
|
436
|
+
this.breadcrumbs.push(internal);
|
|
437
|
+
if (this.breadcrumbs.length > this.maxBreadcrumbs) {
|
|
438
|
+
this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get all breadcrumbs (oldest first)
|
|
443
|
+
*/
|
|
444
|
+
getAll() {
|
|
445
|
+
return [...this.breadcrumbs];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get the last N breadcrumbs
|
|
449
|
+
*/
|
|
450
|
+
getLast(count) {
|
|
451
|
+
return this.breadcrumbs.slice(-count);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Clear all breadcrumbs
|
|
455
|
+
*/
|
|
456
|
+
clear() {
|
|
457
|
+
this.breadcrumbs = [];
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Reset manager
|
|
461
|
+
*/
|
|
462
|
+
reset() {
|
|
463
|
+
this.breadcrumbs = [];
|
|
464
|
+
this.maxBreadcrumbs = 50;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
var breadcrumbManager = null;
|
|
468
|
+
function initBreadcrumbManager(config) {
|
|
469
|
+
if (!breadcrumbManager) {
|
|
470
|
+
breadcrumbManager = new BreadcrumbManager();
|
|
471
|
+
}
|
|
472
|
+
breadcrumbManager.init(config);
|
|
473
|
+
return breadcrumbManager;
|
|
474
|
+
}
|
|
475
|
+
function getBreadcrumbManager() {
|
|
476
|
+
return breadcrumbManager;
|
|
477
|
+
}
|
|
478
|
+
function resetBreadcrumbManager() {
|
|
479
|
+
if (breadcrumbManager) {
|
|
480
|
+
breadcrumbManager.reset();
|
|
481
|
+
}
|
|
482
|
+
breadcrumbManager = null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/breadcrumbs/ConsoleTracker.ts
|
|
486
|
+
var ConsoleTracker = class {
|
|
487
|
+
originalMethods = null;
|
|
488
|
+
started = false;
|
|
489
|
+
/**
|
|
490
|
+
* Start tracking console calls
|
|
491
|
+
*/
|
|
492
|
+
start() {
|
|
493
|
+
if (this.started) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
this.originalMethods = {
|
|
497
|
+
log: console.log.bind(console),
|
|
498
|
+
info: console.info.bind(console),
|
|
499
|
+
warn: console.warn.bind(console),
|
|
500
|
+
error: console.error.bind(console),
|
|
501
|
+
debug: console.debug.bind(console)
|
|
502
|
+
};
|
|
503
|
+
const methods = ["log", "info", "warn", "error", "debug"];
|
|
504
|
+
for (const method of methods) {
|
|
505
|
+
this.wrapConsoleMethod(method);
|
|
506
|
+
}
|
|
507
|
+
this.started = true;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Stop tracking console calls
|
|
511
|
+
*/
|
|
512
|
+
stop() {
|
|
513
|
+
if (!this.started || !this.originalMethods) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
console.log = this.originalMethods.log;
|
|
517
|
+
console.info = this.originalMethods.info;
|
|
518
|
+
console.warn = this.originalMethods.warn;
|
|
519
|
+
console.error = this.originalMethods.error;
|
|
520
|
+
console.debug = this.originalMethods.debug;
|
|
521
|
+
this.originalMethods = null;
|
|
522
|
+
this.started = false;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Wrap a console method to add breadcrumbs
|
|
526
|
+
*/
|
|
527
|
+
wrapConsoleMethod(method) {
|
|
528
|
+
const original = this.originalMethods[method];
|
|
529
|
+
const self = this;
|
|
530
|
+
console[method] = function(...args) {
|
|
531
|
+
self.addBreadcrumb(method, args);
|
|
532
|
+
original(...args);
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Add a breadcrumb for a console call
|
|
537
|
+
*/
|
|
538
|
+
addBreadcrumb(method, args) {
|
|
539
|
+
const manager = getBreadcrumbManager();
|
|
540
|
+
if (!manager) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const level = this.methodToLevel(method);
|
|
544
|
+
const message = this.formatArgs(args);
|
|
545
|
+
manager.add({
|
|
546
|
+
type: "console",
|
|
547
|
+
category: `console.${method}`,
|
|
548
|
+
message,
|
|
549
|
+
level,
|
|
550
|
+
data: {
|
|
551
|
+
arguments: this.serializeArgs(args)
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Map console method to breadcrumb level
|
|
557
|
+
*/
|
|
558
|
+
methodToLevel(method) {
|
|
559
|
+
switch (method) {
|
|
560
|
+
case "error":
|
|
561
|
+
return "error";
|
|
562
|
+
case "warn":
|
|
563
|
+
return "warning";
|
|
564
|
+
case "debug":
|
|
565
|
+
return "debug";
|
|
566
|
+
default:
|
|
567
|
+
return "info";
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Format arguments into a string message
|
|
572
|
+
*/
|
|
573
|
+
formatArgs(args) {
|
|
574
|
+
return args.map((arg) => {
|
|
575
|
+
if (typeof arg === "string") {
|
|
576
|
+
return arg;
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
return JSON.stringify(arg);
|
|
580
|
+
} catch {
|
|
581
|
+
return String(arg);
|
|
582
|
+
}
|
|
583
|
+
}).join(" ").substring(0, 500);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Serialize arguments for data storage
|
|
587
|
+
*/
|
|
588
|
+
serializeArgs(args) {
|
|
589
|
+
return args.map((arg) => {
|
|
590
|
+
if (typeof arg === "string" || typeof arg === "number" || typeof arg === "boolean") {
|
|
591
|
+
return arg;
|
|
592
|
+
}
|
|
593
|
+
if (arg === null || arg === void 0) {
|
|
594
|
+
return arg;
|
|
595
|
+
}
|
|
596
|
+
if (arg instanceof Error) {
|
|
597
|
+
return {
|
|
598
|
+
name: arg.name,
|
|
599
|
+
message: arg.message
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
return JSON.parse(JSON.stringify(arg));
|
|
604
|
+
} catch {
|
|
605
|
+
return "[Unserializable]";
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Reset tracker state
|
|
611
|
+
*/
|
|
612
|
+
reset() {
|
|
613
|
+
this.stop();
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
var consoleTracker = null;
|
|
617
|
+
function getConsoleTracker() {
|
|
618
|
+
if (!consoleTracker) {
|
|
619
|
+
consoleTracker = new ConsoleTracker();
|
|
620
|
+
}
|
|
621
|
+
return consoleTracker;
|
|
622
|
+
}
|
|
623
|
+
function resetConsoleTracker() {
|
|
624
|
+
if (consoleTracker) {
|
|
625
|
+
consoleTracker.reset();
|
|
626
|
+
}
|
|
627
|
+
consoleTracker = null;
|
|
628
|
+
}
|
|
629
|
+
var HttpTracker = class {
|
|
630
|
+
originalHttpRequest = null;
|
|
631
|
+
originalHttpsRequest = null;
|
|
632
|
+
started = false;
|
|
633
|
+
patchFailed = false;
|
|
634
|
+
/**
|
|
635
|
+
* Start tracking HTTP requests
|
|
636
|
+
*/
|
|
637
|
+
start() {
|
|
638
|
+
if (this.started || this.patchFailed) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
this.originalHttpRequest = http__namespace.request;
|
|
643
|
+
this.originalHttpsRequest = https__namespace.request;
|
|
644
|
+
this.wrapRequestMethod(http__namespace, "http");
|
|
645
|
+
this.wrapRequestMethod(https__namespace, "https");
|
|
646
|
+
this.started = true;
|
|
647
|
+
} catch {
|
|
648
|
+
this.patchFailed = true;
|
|
649
|
+
this.originalHttpRequest = null;
|
|
650
|
+
this.originalHttpsRequest = null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Stop tracking HTTP requests
|
|
655
|
+
*/
|
|
656
|
+
stop() {
|
|
657
|
+
if (!this.started) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
if (this.originalHttpRequest) {
|
|
662
|
+
Object.defineProperty(http__namespace, "request", {
|
|
663
|
+
value: this.originalHttpRequest,
|
|
664
|
+
writable: true,
|
|
665
|
+
configurable: true
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (this.originalHttpsRequest) {
|
|
669
|
+
Object.defineProperty(https__namespace, "request", {
|
|
670
|
+
value: this.originalHttpsRequest,
|
|
671
|
+
writable: true,
|
|
672
|
+
configurable: true
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
this.originalHttpRequest = null;
|
|
678
|
+
this.originalHttpsRequest = null;
|
|
679
|
+
this.started = false;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Wrap a module's request method
|
|
683
|
+
*/
|
|
684
|
+
wrapRequestMethod(mod, _protocol) {
|
|
685
|
+
const originalRequest = mod.request.bind(mod);
|
|
686
|
+
const self = this;
|
|
687
|
+
const wrappedRequest = function(...args) {
|
|
688
|
+
const [urlOrOptions, optionsOrCallback, maybeCallback] = args;
|
|
689
|
+
const { url, method, hostname: hostname3 } = self.parseRequestArgs(
|
|
690
|
+
urlOrOptions,
|
|
691
|
+
optionsOrCallback
|
|
692
|
+
);
|
|
693
|
+
const startTime = Date.now();
|
|
694
|
+
const req = originalRequest.apply(mod, args);
|
|
695
|
+
req.on("response", (res) => {
|
|
696
|
+
const duration = Date.now() - startTime;
|
|
697
|
+
self.addBreadcrumb(method, url, hostname3, res.statusCode, duration);
|
|
698
|
+
});
|
|
699
|
+
req.on("error", (error) => {
|
|
700
|
+
const duration = Date.now() - startTime;
|
|
701
|
+
self.addErrorBreadcrumb(method, url, hostname3, error, duration);
|
|
702
|
+
});
|
|
703
|
+
return req;
|
|
704
|
+
};
|
|
705
|
+
Object.defineProperty(mod, "request", {
|
|
706
|
+
value: wrappedRequest,
|
|
707
|
+
writable: true,
|
|
708
|
+
configurable: true
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Parse request arguments to extract URL info
|
|
713
|
+
*/
|
|
714
|
+
parseRequestArgs(urlOrOptions, optionsOrCallback) {
|
|
715
|
+
let url = "";
|
|
716
|
+
let method = "GET";
|
|
717
|
+
let hostname3 = "";
|
|
718
|
+
if (typeof urlOrOptions === "string") {
|
|
719
|
+
url = urlOrOptions;
|
|
720
|
+
try {
|
|
721
|
+
const parsed = new URL(urlOrOptions);
|
|
722
|
+
hostname3 = parsed.hostname;
|
|
723
|
+
} catch {
|
|
724
|
+
hostname3 = urlOrOptions;
|
|
725
|
+
}
|
|
726
|
+
} else if (urlOrOptions instanceof URL) {
|
|
727
|
+
url = urlOrOptions.toString();
|
|
728
|
+
hostname3 = urlOrOptions.hostname;
|
|
729
|
+
} else if (typeof urlOrOptions === "object") {
|
|
730
|
+
const opts = urlOrOptions;
|
|
731
|
+
hostname3 = opts.hostname || opts.host || "localhost";
|
|
732
|
+
const port = opts.port ? `:${opts.port}` : "";
|
|
733
|
+
const path2 = opts.path || "/";
|
|
734
|
+
const protocol = opts.protocol || "http:";
|
|
735
|
+
url = `${protocol}//${hostname3}${port}${path2}`;
|
|
736
|
+
method = opts.method || "GET";
|
|
737
|
+
}
|
|
738
|
+
if (optionsOrCallback && typeof optionsOrCallback === "object" && optionsOrCallback.method) {
|
|
739
|
+
method = optionsOrCallback.method;
|
|
740
|
+
}
|
|
741
|
+
return { url, method: method.toUpperCase(), hostname: hostname3 };
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Add a breadcrumb for a successful HTTP request
|
|
745
|
+
*/
|
|
746
|
+
addBreadcrumb(method, url, hostname3, statusCode, duration) {
|
|
747
|
+
const manager = getBreadcrumbManager();
|
|
748
|
+
if (!manager) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
manager.add({
|
|
752
|
+
type: "http",
|
|
753
|
+
category: "http.client",
|
|
754
|
+
message: `${method} ${url}`,
|
|
755
|
+
level: statusCode && statusCode >= 400 ? "warning" : "info",
|
|
756
|
+
data: {
|
|
757
|
+
method,
|
|
758
|
+
url: this.truncateUrl(url),
|
|
759
|
+
hostname: hostname3,
|
|
760
|
+
status_code: statusCode,
|
|
761
|
+
duration_ms: duration
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Add a breadcrumb for a failed HTTP request
|
|
767
|
+
*/
|
|
768
|
+
addErrorBreadcrumb(method, url, hostname3, error, duration) {
|
|
769
|
+
const manager = getBreadcrumbManager();
|
|
770
|
+
if (!manager) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
manager.add({
|
|
774
|
+
type: "http",
|
|
775
|
+
category: "http.client",
|
|
776
|
+
message: `${method} ${url} failed: ${error.message}`,
|
|
777
|
+
level: "error",
|
|
778
|
+
data: {
|
|
779
|
+
method,
|
|
780
|
+
url: this.truncateUrl(url),
|
|
781
|
+
hostname: hostname3,
|
|
782
|
+
error: error.message,
|
|
783
|
+
duration_ms: duration
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Truncate URL for storage
|
|
789
|
+
*/
|
|
790
|
+
truncateUrl(url) {
|
|
791
|
+
const maxLength = 200;
|
|
792
|
+
if (url.length <= maxLength) {
|
|
793
|
+
return url;
|
|
794
|
+
}
|
|
795
|
+
return url.substring(0, maxLength) + "...";
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Reset tracker state
|
|
799
|
+
*/
|
|
800
|
+
reset() {
|
|
801
|
+
this.stop();
|
|
802
|
+
this.patchFailed = false;
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
var httpTracker = null;
|
|
806
|
+
function getHttpTracker() {
|
|
807
|
+
if (!httpTracker) {
|
|
808
|
+
httpTracker = new HttpTracker();
|
|
809
|
+
}
|
|
810
|
+
return httpTracker;
|
|
811
|
+
}
|
|
812
|
+
function resetHttpTracker() {
|
|
813
|
+
if (httpTracker) {
|
|
814
|
+
httpTracker.reset();
|
|
815
|
+
}
|
|
816
|
+
httpTracker = null;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/capture/ProcessCapture.ts
|
|
820
|
+
var ProcessCapture = class {
|
|
821
|
+
handler = null;
|
|
822
|
+
uncaughtHandler = null;
|
|
823
|
+
rejectionHandler = null;
|
|
824
|
+
started = false;
|
|
825
|
+
exitOnUncaught = true;
|
|
826
|
+
/**
|
|
827
|
+
* Start capturing process errors
|
|
828
|
+
*/
|
|
829
|
+
start(handler, exitOnUncaught = true) {
|
|
830
|
+
if (this.started) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
this.handler = handler;
|
|
834
|
+
this.exitOnUncaught = exitOnUncaught;
|
|
835
|
+
this.uncaughtHandler = this.handleUncaughtException.bind(this);
|
|
836
|
+
this.rejectionHandler = this.handleUnhandledRejection.bind(this);
|
|
837
|
+
process.on("uncaughtException", this.uncaughtHandler);
|
|
838
|
+
process.on("unhandledRejection", this.rejectionHandler);
|
|
839
|
+
this.started = true;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Start only uncaughtException capture
|
|
843
|
+
*/
|
|
844
|
+
startUncaughtException(handler, exitOnUncaught = true) {
|
|
845
|
+
if (this.uncaughtHandler) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
this.handler = handler;
|
|
849
|
+
this.exitOnUncaught = exitOnUncaught;
|
|
850
|
+
this.uncaughtHandler = this.handleUncaughtException.bind(this);
|
|
851
|
+
process.on("uncaughtException", this.uncaughtHandler);
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Start only unhandledRejection capture
|
|
855
|
+
*/
|
|
856
|
+
startUnhandledRejection(handler) {
|
|
857
|
+
if (this.rejectionHandler) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
this.handler = handler;
|
|
861
|
+
this.rejectionHandler = this.handleUnhandledRejection.bind(this);
|
|
862
|
+
process.on("unhandledRejection", this.rejectionHandler);
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Stop capturing process errors
|
|
866
|
+
*/
|
|
867
|
+
stop() {
|
|
868
|
+
if (this.uncaughtHandler) {
|
|
869
|
+
process.off("uncaughtException", this.uncaughtHandler);
|
|
870
|
+
this.uncaughtHandler = null;
|
|
871
|
+
}
|
|
872
|
+
if (this.rejectionHandler) {
|
|
873
|
+
process.off("unhandledRejection", this.rejectionHandler);
|
|
874
|
+
this.rejectionHandler = null;
|
|
875
|
+
}
|
|
876
|
+
this.handler = null;
|
|
877
|
+
this.started = false;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Handle uncaught exception
|
|
881
|
+
*/
|
|
882
|
+
handleUncaughtException(error) {
|
|
883
|
+
if (!this.handler) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const captured = {
|
|
887
|
+
error,
|
|
888
|
+
message: error.message,
|
|
889
|
+
type: "uncaughtException",
|
|
890
|
+
severity: "critical"
|
|
891
|
+
};
|
|
892
|
+
try {
|
|
893
|
+
this.handler(captured);
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
if (this.exitOnUncaught) {
|
|
897
|
+
setTimeout(() => {
|
|
898
|
+
process.exit(1);
|
|
899
|
+
}, 100);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Handle unhandled rejection
|
|
904
|
+
*/
|
|
905
|
+
handleUnhandledRejection(reason, promise) {
|
|
906
|
+
if (!this.handler) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
910
|
+
const captured = {
|
|
911
|
+
error,
|
|
912
|
+
message: error.message,
|
|
913
|
+
type: "unhandledRejection",
|
|
914
|
+
severity: "error",
|
|
915
|
+
promise
|
|
916
|
+
};
|
|
917
|
+
try {
|
|
918
|
+
this.handler(captured);
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Reset capture state
|
|
924
|
+
*/
|
|
925
|
+
reset() {
|
|
926
|
+
this.stop();
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
var processCapture = null;
|
|
930
|
+
function getProcessCapture() {
|
|
931
|
+
if (!processCapture) {
|
|
932
|
+
processCapture = new ProcessCapture();
|
|
933
|
+
}
|
|
934
|
+
return processCapture;
|
|
935
|
+
}
|
|
936
|
+
function resetProcessCapture() {
|
|
937
|
+
if (processCapture) {
|
|
938
|
+
processCapture.reset();
|
|
939
|
+
}
|
|
940
|
+
processCapture = null;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/capture/ConsoleCapture.ts
|
|
944
|
+
var ConsoleCapture = class {
|
|
945
|
+
handler = null;
|
|
946
|
+
originalConsoleError = null;
|
|
947
|
+
started = false;
|
|
948
|
+
/**
|
|
949
|
+
* Start capturing console.error calls
|
|
950
|
+
*/
|
|
951
|
+
start(handler) {
|
|
952
|
+
if (this.started) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
this.handler = handler;
|
|
956
|
+
this.originalConsoleError = console.error.bind(console);
|
|
957
|
+
const self = this;
|
|
958
|
+
console.error = function(...args) {
|
|
959
|
+
self.originalConsoleError(...args);
|
|
960
|
+
self.handleConsoleError(args);
|
|
961
|
+
};
|
|
962
|
+
this.started = true;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Stop capturing console.error
|
|
966
|
+
*/
|
|
967
|
+
stop() {
|
|
968
|
+
if (!this.started || !this.originalConsoleError) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
console.error = this.originalConsoleError;
|
|
972
|
+
this.originalConsoleError = null;
|
|
973
|
+
this.handler = null;
|
|
974
|
+
this.started = false;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Handle console.error call
|
|
978
|
+
*/
|
|
979
|
+
handleConsoleError(args) {
|
|
980
|
+
if (!this.handler) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
let error;
|
|
984
|
+
let message;
|
|
985
|
+
const firstArg = args[0];
|
|
986
|
+
if (firstArg instanceof Error) {
|
|
987
|
+
error = firstArg;
|
|
988
|
+
message = error.message;
|
|
989
|
+
} else {
|
|
990
|
+
message = args.map((arg) => {
|
|
991
|
+
if (typeof arg === "string") return arg;
|
|
992
|
+
try {
|
|
993
|
+
return JSON.stringify(arg);
|
|
994
|
+
} catch {
|
|
995
|
+
return String(arg);
|
|
996
|
+
}
|
|
997
|
+
}).join(" ");
|
|
998
|
+
error = new Error(message);
|
|
999
|
+
}
|
|
1000
|
+
const captured = {
|
|
1001
|
+
error,
|
|
1002
|
+
message,
|
|
1003
|
+
type: "unhandledRejection",
|
|
1004
|
+
// Use this type for console errors
|
|
1005
|
+
severity: "error"
|
|
1006
|
+
};
|
|
1007
|
+
try {
|
|
1008
|
+
this.handler(captured);
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Reset capture state
|
|
1014
|
+
*/
|
|
1015
|
+
reset() {
|
|
1016
|
+
this.stop();
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
var consoleCapture = null;
|
|
1020
|
+
function getConsoleCapture() {
|
|
1021
|
+
if (!consoleCapture) {
|
|
1022
|
+
consoleCapture = new ConsoleCapture();
|
|
1023
|
+
}
|
|
1024
|
+
return consoleCapture;
|
|
1025
|
+
}
|
|
1026
|
+
function resetConsoleCapture() {
|
|
1027
|
+
if (consoleCapture) {
|
|
1028
|
+
consoleCapture.reset();
|
|
1029
|
+
}
|
|
1030
|
+
consoleCapture = null;
|
|
1031
|
+
}
|
|
1032
|
+
var HmacSigner = class {
|
|
1033
|
+
secret;
|
|
1034
|
+
constructor(secret) {
|
|
1035
|
+
this.secret = secret;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Sign a payload with HMAC-SHA256
|
|
1039
|
+
* @param payload - The JSON payload string to sign
|
|
1040
|
+
* @param timestamp - Unix timestamp (defaults to current time)
|
|
1041
|
+
* @returns Hex-encoded signature
|
|
1042
|
+
*/
|
|
1043
|
+
sign(payload, timestamp) {
|
|
1044
|
+
const ts = timestamp ?? Math.floor(Date.now() / 1e3);
|
|
1045
|
+
const signedPayload = `${ts}.${payload}`;
|
|
1046
|
+
const hmac = crypto__namespace.createHmac("sha256", this.secret);
|
|
1047
|
+
hmac.update(signedPayload);
|
|
1048
|
+
return hmac.digest("hex");
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Build headers for a signed request
|
|
1052
|
+
* @param payload - The JSON payload string
|
|
1053
|
+
* @returns Headers object with signature and timestamp
|
|
1054
|
+
*/
|
|
1055
|
+
buildHeaders(payload) {
|
|
1056
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1057
|
+
const signature = this.sign(payload, timestamp);
|
|
1058
|
+
return {
|
|
1059
|
+
"X-Webhook-Signature": signature,
|
|
1060
|
+
"X-Webhook-Timestamp": String(timestamp)
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Verify a signature (useful for testing)
|
|
1065
|
+
* @param payload - The payload that was signed
|
|
1066
|
+
* @param signature - The signature to verify
|
|
1067
|
+
* @param timestamp - The timestamp used in signing
|
|
1068
|
+
* @param maxAge - Maximum age in seconds (default: 5 minutes)
|
|
1069
|
+
*/
|
|
1070
|
+
verify(payload, signature, timestamp, maxAge = 300) {
|
|
1071
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1072
|
+
if (Math.abs(now - timestamp) > maxAge) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
const expected = this.sign(payload, timestamp);
|
|
1076
|
+
return crypto__namespace.timingSafeEqual(
|
|
1077
|
+
Buffer.from(signature, "hex"),
|
|
1078
|
+
Buffer.from(expected, "hex")
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
// src/transport/HttpTransport.ts
|
|
1084
|
+
var HttpTransport = class {
|
|
1085
|
+
endpoint;
|
|
1086
|
+
token;
|
|
1087
|
+
timeout;
|
|
1088
|
+
maxRetries;
|
|
1089
|
+
hmacSigner = null;
|
|
1090
|
+
debug;
|
|
1091
|
+
constructor(options) {
|
|
1092
|
+
this.endpoint = options.endpoint;
|
|
1093
|
+
this.token = options.token;
|
|
1094
|
+
this.timeout = options.timeout;
|
|
1095
|
+
this.maxRetries = options.maxRetries;
|
|
1096
|
+
this.debug = options.debug ?? false;
|
|
1097
|
+
if (options.hmacSecret) {
|
|
1098
|
+
this.hmacSigner = new HmacSigner(options.hmacSecret);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Send an event to the Error Explorer API
|
|
1103
|
+
*/
|
|
1104
|
+
async send(event) {
|
|
1105
|
+
const payload = JSON.stringify(event);
|
|
1106
|
+
let lastError = null;
|
|
1107
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
1108
|
+
try {
|
|
1109
|
+
const success = await this.doSend(payload);
|
|
1110
|
+
if (success) {
|
|
1111
|
+
if (this.debug) {
|
|
1112
|
+
console.log("[ErrorExplorer] Event sent successfully");
|
|
1113
|
+
}
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
lastError = error;
|
|
1118
|
+
if (this.debug) {
|
|
1119
|
+
console.error(`[ErrorExplorer] Send attempt ${attempt + 1} failed:`, error);
|
|
1120
|
+
}
|
|
1121
|
+
if (attempt < this.maxRetries) {
|
|
1122
|
+
await this.sleep(Math.pow(2, attempt) * 100);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (this.debug && lastError) {
|
|
1127
|
+
console.error("[ErrorExplorer] All send attempts failed:", lastError);
|
|
1128
|
+
}
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Perform the actual HTTP request
|
|
1133
|
+
*/
|
|
1134
|
+
doSend(payload) {
|
|
1135
|
+
return new Promise((resolve, reject) => {
|
|
1136
|
+
const url = new URL(this.endpoint);
|
|
1137
|
+
const isHttps = url.protocol === "https:";
|
|
1138
|
+
const transport = isHttps ? https__namespace : http__namespace;
|
|
1139
|
+
const headers = {
|
|
1140
|
+
"Content-Type": "application/json",
|
|
1141
|
+
"Content-Length": String(Buffer.byteLength(payload)),
|
|
1142
|
+
"X-Webhook-Token": this.token,
|
|
1143
|
+
"User-Agent": "@error-explorer/node/1.0.0",
|
|
1144
|
+
// Include Host header for proper virtual host routing
|
|
1145
|
+
"Host": url.host
|
|
1146
|
+
};
|
|
1147
|
+
if (this.hmacSigner) {
|
|
1148
|
+
const hmacHeaders = this.hmacSigner.buildHeaders(payload);
|
|
1149
|
+
Object.assign(headers, hmacHeaders);
|
|
1150
|
+
}
|
|
1151
|
+
const options = {
|
|
1152
|
+
method: "POST",
|
|
1153
|
+
hostname: url.hostname,
|
|
1154
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
1155
|
+
path: url.pathname,
|
|
1156
|
+
headers,
|
|
1157
|
+
timeout: this.timeout
|
|
1158
|
+
};
|
|
1159
|
+
const req = transport.request(options, (res) => {
|
|
1160
|
+
let data = "";
|
|
1161
|
+
res.on("data", (chunk) => {
|
|
1162
|
+
data += chunk;
|
|
1163
|
+
});
|
|
1164
|
+
res.on("end", () => {
|
|
1165
|
+
const statusCode = res.statusCode || 0;
|
|
1166
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
1167
|
+
resolve(true);
|
|
1168
|
+
} else if (statusCode === 429) {
|
|
1169
|
+
const retryAfter = res.headers["retry-after"];
|
|
1170
|
+
reject(new Error(`Rate limited. Retry after: ${retryAfter}`));
|
|
1171
|
+
} else if (statusCode >= 500) {
|
|
1172
|
+
reject(new Error(`Server error: ${statusCode}`));
|
|
1173
|
+
} else {
|
|
1174
|
+
if (this.debug) {
|
|
1175
|
+
console.error(`[ErrorExplorer] Client error ${statusCode}:`, data);
|
|
1176
|
+
}
|
|
1177
|
+
resolve(false);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
req.on("error", (error) => {
|
|
1182
|
+
reject(error);
|
|
1183
|
+
});
|
|
1184
|
+
req.on("timeout", () => {
|
|
1185
|
+
req.destroy();
|
|
1186
|
+
reject(new Error("Request timeout"));
|
|
1187
|
+
});
|
|
1188
|
+
req.write(payload);
|
|
1189
|
+
req.end();
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Send synchronously (blocking) - for use in exit handlers
|
|
1194
|
+
*/
|
|
1195
|
+
sendSync(event) {
|
|
1196
|
+
this.send(event).catch((err) => {
|
|
1197
|
+
if (this.debug) {
|
|
1198
|
+
console.error("[ErrorExplorer] Sync send failed:", err);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Sleep for a given duration
|
|
1204
|
+
*/
|
|
1205
|
+
sleep(ms) {
|
|
1206
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
// src/ErrorExplorer.ts
|
|
1211
|
+
var SDK_NAME = "@error-explorer/node";
|
|
1212
|
+
var SDK_VERSION = "1.0.0";
|
|
1213
|
+
var ErrorExplorerClient = class {
|
|
1214
|
+
config = null;
|
|
1215
|
+
transport = null;
|
|
1216
|
+
initialized = false;
|
|
1217
|
+
tags = {};
|
|
1218
|
+
extra = {};
|
|
1219
|
+
contexts = {};
|
|
1220
|
+
/**
|
|
1221
|
+
* Initialize the SDK
|
|
1222
|
+
*/
|
|
1223
|
+
init(options) {
|
|
1224
|
+
if (this.initialized) {
|
|
1225
|
+
console.warn("[ErrorExplorer] Already initialized");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
this.config = resolveConfig(options);
|
|
1230
|
+
if (this.config.serverName) {
|
|
1231
|
+
setServerName(this.config.serverName);
|
|
1232
|
+
}
|
|
1233
|
+
this.transport = new HttpTransport({
|
|
1234
|
+
endpoint: this.config.endpoint,
|
|
1235
|
+
token: this.config.token,
|
|
1236
|
+
timeout: this.config.timeout,
|
|
1237
|
+
maxRetries: this.config.maxRetries,
|
|
1238
|
+
hmacSecret: this.config.hmacSecret,
|
|
1239
|
+
debug: this.config.debug
|
|
1240
|
+
});
|
|
1241
|
+
if (this.config.breadcrumbs.enabled) {
|
|
1242
|
+
initBreadcrumbManager(this.config);
|
|
1243
|
+
this.startBreadcrumbTrackers();
|
|
1244
|
+
}
|
|
1245
|
+
if (this.config.autoCapture.uncaughtExceptions || this.config.autoCapture.unhandledRejections) {
|
|
1246
|
+
const processCapture2 = getProcessCapture();
|
|
1247
|
+
if (this.config.autoCapture.uncaughtExceptions) {
|
|
1248
|
+
processCapture2.startUncaughtException(
|
|
1249
|
+
this.handleCapturedError.bind(this),
|
|
1250
|
+
this.config.exitOnUncaughtException
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
if (this.config.autoCapture.unhandledRejections) {
|
|
1254
|
+
processCapture2.startUnhandledRejection(this.handleCapturedError.bind(this));
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
if (this.config.autoCapture.console) {
|
|
1258
|
+
getConsoleCapture().start(this.handleCapturedError.bind(this));
|
|
1259
|
+
}
|
|
1260
|
+
this.initialized = true;
|
|
1261
|
+
if (this.config.debug) {
|
|
1262
|
+
console.log("[ErrorExplorer] Initialized", {
|
|
1263
|
+
endpoint: this.config.endpoint,
|
|
1264
|
+
environment: this.config.environment,
|
|
1265
|
+
serverName: this.config.serverName
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
console.error("[ErrorExplorer] Initialization failed:", error);
|
|
1270
|
+
throw error;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Check if SDK is initialized
|
|
1275
|
+
*/
|
|
1276
|
+
isInitialized() {
|
|
1277
|
+
return this.initialized;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Set user context
|
|
1281
|
+
*/
|
|
1282
|
+
setUser(user) {
|
|
1283
|
+
getUserContextManager().setUser(user);
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Clear user context
|
|
1287
|
+
*/
|
|
1288
|
+
clearUser() {
|
|
1289
|
+
getUserContextManager().clearUser();
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Set a single tag
|
|
1293
|
+
*/
|
|
1294
|
+
setTag(key, value) {
|
|
1295
|
+
this.tags[key] = value;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Set multiple tags
|
|
1299
|
+
*/
|
|
1300
|
+
setTags(tags) {
|
|
1301
|
+
this.tags = { ...this.tags, ...tags };
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Set extra data
|
|
1305
|
+
*/
|
|
1306
|
+
setExtra(key, value) {
|
|
1307
|
+
this.extra[key] = value;
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Set a named context
|
|
1311
|
+
*/
|
|
1312
|
+
setContext(name, context) {
|
|
1313
|
+
this.contexts[name] = context;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Add a manual breadcrumb
|
|
1317
|
+
*/
|
|
1318
|
+
addBreadcrumb(breadcrumb) {
|
|
1319
|
+
const manager = getBreadcrumbManager();
|
|
1320
|
+
if (manager) {
|
|
1321
|
+
manager.add(breadcrumb);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Capture an exception manually
|
|
1326
|
+
*/
|
|
1327
|
+
captureException(error, context) {
|
|
1328
|
+
if (!this.initialized || !this.config) {
|
|
1329
|
+
console.warn("[ErrorExplorer] Not initialized");
|
|
1330
|
+
return "";
|
|
1331
|
+
}
|
|
1332
|
+
const eventId = generateUuid();
|
|
1333
|
+
const event = this.buildEvent(error, context, "error");
|
|
1334
|
+
this.processAndSend(event);
|
|
1335
|
+
return eventId;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Capture a message manually
|
|
1339
|
+
*/
|
|
1340
|
+
captureMessage(message, level = "info") {
|
|
1341
|
+
if (!this.initialized || !this.config) {
|
|
1342
|
+
console.warn("[ErrorExplorer] Not initialized");
|
|
1343
|
+
return "";
|
|
1344
|
+
}
|
|
1345
|
+
const eventId = generateUuid();
|
|
1346
|
+
const event = this.buildEvent(new Error(message), void 0, level);
|
|
1347
|
+
event.exception_class = "Message";
|
|
1348
|
+
this.processAndSend(event);
|
|
1349
|
+
return eventId;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Flush all pending events (best effort)
|
|
1353
|
+
*/
|
|
1354
|
+
async flush(timeout = 5e3) {
|
|
1355
|
+
if (!this.initialized) {
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(timeout, 100)));
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Close the SDK and cleanup
|
|
1363
|
+
*/
|
|
1364
|
+
async close(timeout = 5e3) {
|
|
1365
|
+
if (!this.initialized) {
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
await this.flush(timeout);
|
|
1369
|
+
this.stopAllTrackers();
|
|
1370
|
+
resetBreadcrumbManager();
|
|
1371
|
+
resetProcessCapture();
|
|
1372
|
+
resetConsoleCapture();
|
|
1373
|
+
resetConsoleTracker();
|
|
1374
|
+
resetHttpTracker();
|
|
1375
|
+
resetUserContextManager();
|
|
1376
|
+
resetServerContext();
|
|
1377
|
+
resetOsContext();
|
|
1378
|
+
resetRuntimeContext();
|
|
1379
|
+
this.config = null;
|
|
1380
|
+
this.transport = null;
|
|
1381
|
+
this.initialized = false;
|
|
1382
|
+
this.tags = {};
|
|
1383
|
+
this.extra = {};
|
|
1384
|
+
this.contexts = {};
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Handle a captured error from auto-capture
|
|
1389
|
+
*/
|
|
1390
|
+
handleCapturedError(captured) {
|
|
1391
|
+
if (!this.config) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
if (this.shouldIgnoreError(captured.message)) {
|
|
1395
|
+
if (this.config.debug) {
|
|
1396
|
+
console.log("[ErrorExplorer] Ignoring error:", captured.message);
|
|
1397
|
+
}
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const event = this.buildEvent(captured.error, void 0, captured.severity);
|
|
1401
|
+
event.tags = {
|
|
1402
|
+
...event.tags,
|
|
1403
|
+
capture_type: captured.type
|
|
1404
|
+
};
|
|
1405
|
+
this.processAndSend(event);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Check if an error should be ignored
|
|
1409
|
+
*/
|
|
1410
|
+
shouldIgnoreError(message) {
|
|
1411
|
+
if (!this.config) {
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
return matchesPattern(message, this.config.ignoreErrors);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Build a complete error event
|
|
1418
|
+
*/
|
|
1419
|
+
buildEvent(error, context, severity = "error") {
|
|
1420
|
+
const config = this.config;
|
|
1421
|
+
const message = getErrorMessage(error);
|
|
1422
|
+
const name = getErrorName(error);
|
|
1423
|
+
const stack = getErrorStack(error);
|
|
1424
|
+
const frames = parseStackTrace(stack);
|
|
1425
|
+
const topFrame = frames[0];
|
|
1426
|
+
const event = {
|
|
1427
|
+
message,
|
|
1428
|
+
exception_class: name,
|
|
1429
|
+
file: topFrame?.filename,
|
|
1430
|
+
line: topFrame?.lineno,
|
|
1431
|
+
column: topFrame?.colno,
|
|
1432
|
+
stack_trace: stack,
|
|
1433
|
+
frames,
|
|
1434
|
+
severity: context?.level ?? severity,
|
|
1435
|
+
environment: config.environment,
|
|
1436
|
+
release: config.release || void 0,
|
|
1437
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1438
|
+
// User context
|
|
1439
|
+
user: context?.user ?? getUserContextManager().getUser() ?? void 0,
|
|
1440
|
+
// Server context
|
|
1441
|
+
server: getServerContext(),
|
|
1442
|
+
// Runtime context
|
|
1443
|
+
runtime: collectRuntimeContext(),
|
|
1444
|
+
// OS context
|
|
1445
|
+
os: collectOsContext(),
|
|
1446
|
+
// Process context
|
|
1447
|
+
process: collectProcessContext(),
|
|
1448
|
+
// Request context (if provided)
|
|
1449
|
+
request: context?.request ? extractRequestContext(context.request) : void 0,
|
|
1450
|
+
// Breadcrumbs
|
|
1451
|
+
breadcrumbs: getBreadcrumbManager()?.getAll(),
|
|
1452
|
+
// Tags (merge global + context)
|
|
1453
|
+
tags: { ...this.tags, ...context?.tags },
|
|
1454
|
+
// Extra data
|
|
1455
|
+
extra: { ...this.extra, ...context?.extra },
|
|
1456
|
+
// Custom contexts
|
|
1457
|
+
contexts: this.contexts,
|
|
1458
|
+
// SDK info
|
|
1459
|
+
sdk: {
|
|
1460
|
+
name: SDK_NAME,
|
|
1461
|
+
version: SDK_VERSION
|
|
1462
|
+
},
|
|
1463
|
+
// Fingerprint for grouping
|
|
1464
|
+
fingerprint: context?.fingerprint
|
|
1465
|
+
};
|
|
1466
|
+
return event;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Process event through beforeSend and send
|
|
1470
|
+
*/
|
|
1471
|
+
async processAndSend(event) {
|
|
1472
|
+
if (!this.config || !this.transport) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
let processedEvent = event;
|
|
1476
|
+
if (this.config.beforeSend) {
|
|
1477
|
+
try {
|
|
1478
|
+
const result = this.config.beforeSend(event);
|
|
1479
|
+
processedEvent = result instanceof Promise ? await result : result;
|
|
1480
|
+
} catch (e) {
|
|
1481
|
+
console.error("[ErrorExplorer] beforeSend threw an error:", e);
|
|
1482
|
+
processedEvent = event;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (!processedEvent) {
|
|
1486
|
+
if (this.config.debug) {
|
|
1487
|
+
console.log("[ErrorExplorer] Event dropped by beforeSend");
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
await this.transport.send(processedEvent);
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Start all breadcrumb trackers
|
|
1495
|
+
*/
|
|
1496
|
+
startBreadcrumbTrackers() {
|
|
1497
|
+
if (!this.config) {
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
const bc = this.config.breadcrumbs;
|
|
1501
|
+
if (bc.console) {
|
|
1502
|
+
getConsoleTracker().start();
|
|
1503
|
+
}
|
|
1504
|
+
if (bc.http) {
|
|
1505
|
+
getHttpTracker().start();
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Stop all breadcrumb trackers
|
|
1510
|
+
*/
|
|
1511
|
+
stopAllTrackers() {
|
|
1512
|
+
resetConsoleTracker();
|
|
1513
|
+
resetHttpTracker();
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
var ErrorExplorer = new ErrorExplorerClient();
|
|
1517
|
+
|
|
1518
|
+
// src/middleware/express.ts
|
|
1519
|
+
function requestHandler(options = {}) {
|
|
1520
|
+
const { extractUser, breadcrumbs = true } = options;
|
|
1521
|
+
return (req, res, next) => {
|
|
1522
|
+
const transaction = generateTransactionId();
|
|
1523
|
+
req.errorExplorer = {
|
|
1524
|
+
transaction,
|
|
1525
|
+
startTime: Date.now()
|
|
1526
|
+
};
|
|
1527
|
+
if (extractUser) {
|
|
1528
|
+
try {
|
|
1529
|
+
const user = extractUser(req);
|
|
1530
|
+
if (user) {
|
|
1531
|
+
req.errorExplorer.user = user;
|
|
1532
|
+
}
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (breadcrumbs) {
|
|
1537
|
+
const manager = getBreadcrumbManager();
|
|
1538
|
+
if (manager) {
|
|
1539
|
+
manager.add({
|
|
1540
|
+
type: "http",
|
|
1541
|
+
category: "http.request",
|
|
1542
|
+
message: `${req.method} ${req.path || req.url}`,
|
|
1543
|
+
level: "info",
|
|
1544
|
+
data: {
|
|
1545
|
+
method: req.method,
|
|
1546
|
+
url: req.originalUrl || req.url,
|
|
1547
|
+
transaction
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
if (breadcrumbs) {
|
|
1553
|
+
const originalEnd = res.end;
|
|
1554
|
+
let ended = false;
|
|
1555
|
+
res.end = function(...args) {
|
|
1556
|
+
if (!ended) {
|
|
1557
|
+
ended = true;
|
|
1558
|
+
const duration = req.errorExplorer?.startTime ? Date.now() - req.errorExplorer.startTime : void 0;
|
|
1559
|
+
const manager = getBreadcrumbManager();
|
|
1560
|
+
if (manager) {
|
|
1561
|
+
manager.add({
|
|
1562
|
+
type: "http",
|
|
1563
|
+
category: "http.response",
|
|
1564
|
+
message: `${req.method} ${req.path || req.url} - ${res.statusCode}`,
|
|
1565
|
+
level: res.statusCode >= 400 ? "warning" : "info",
|
|
1566
|
+
data: {
|
|
1567
|
+
method: req.method,
|
|
1568
|
+
url: req.originalUrl || req.url,
|
|
1569
|
+
status_code: res.statusCode,
|
|
1570
|
+
duration_ms: duration,
|
|
1571
|
+
transaction
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return originalEnd.apply(res, args);
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
next();
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
function errorHandler(options = {}) {
|
|
1583
|
+
const { callNext = true, onError } = options;
|
|
1584
|
+
return (error, req, res, next) => {
|
|
1585
|
+
if (res.headersSent) {
|
|
1586
|
+
if (callNext) {
|
|
1587
|
+
next(error);
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
const requestContext = extractRequestContext(req);
|
|
1592
|
+
const user = req.errorExplorer?.user;
|
|
1593
|
+
const transaction = req.errorExplorer?.transaction;
|
|
1594
|
+
ErrorExplorer.captureException(error, {
|
|
1595
|
+
request: req,
|
|
1596
|
+
user,
|
|
1597
|
+
tags: {
|
|
1598
|
+
transaction: transaction || "unknown",
|
|
1599
|
+
route: req.path || req.url || "unknown",
|
|
1600
|
+
method: req.method || "unknown"
|
|
1601
|
+
},
|
|
1602
|
+
extra: {
|
|
1603
|
+
requestContext,
|
|
1604
|
+
query: req.query,
|
|
1605
|
+
params: req.params
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
if (onError) {
|
|
1609
|
+
try {
|
|
1610
|
+
onError(error, req, res);
|
|
1611
|
+
} catch {
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (callNext) {
|
|
1615
|
+
next(error);
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function setupExpress(requestOptions = {}, errorOptions = {}) {
|
|
1620
|
+
return {
|
|
1621
|
+
requestHandler: requestHandler(requestOptions),
|
|
1622
|
+
errorHandler: errorHandler(errorOptions)
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/middleware/http.ts
|
|
1627
|
+
function wrapHandler(handler, options = {}) {
|
|
1628
|
+
const { extractUser, breadcrumbs = true } = options;
|
|
1629
|
+
return async (req, res) => {
|
|
1630
|
+
const transaction = generateTransactionId();
|
|
1631
|
+
const startTime = Date.now();
|
|
1632
|
+
if (breadcrumbs) {
|
|
1633
|
+
const manager = getBreadcrumbManager();
|
|
1634
|
+
if (manager) {
|
|
1635
|
+
manager.add({
|
|
1636
|
+
type: "http",
|
|
1637
|
+
category: "http.request",
|
|
1638
|
+
message: `${req.method} ${req.url}`,
|
|
1639
|
+
level: "info",
|
|
1640
|
+
data: {
|
|
1641
|
+
method: req.method,
|
|
1642
|
+
url: req.url,
|
|
1643
|
+
transaction
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
let user;
|
|
1649
|
+
if (extractUser) {
|
|
1650
|
+
try {
|
|
1651
|
+
user = extractUser(req);
|
|
1652
|
+
} catch {
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
try {
|
|
1656
|
+
await handler(req, res);
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
ErrorExplorer.captureException(error, {
|
|
1659
|
+
request: req,
|
|
1660
|
+
user: user || void 0,
|
|
1661
|
+
tags: {
|
|
1662
|
+
transaction,
|
|
1663
|
+
method: req.method || "unknown"
|
|
1664
|
+
},
|
|
1665
|
+
extra: {
|
|
1666
|
+
requestContext: extractRequestContext(req)
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
throw error;
|
|
1670
|
+
} finally {
|
|
1671
|
+
if (breadcrumbs) {
|
|
1672
|
+
const duration = Date.now() - startTime;
|
|
1673
|
+
const manager = getBreadcrumbManager();
|
|
1674
|
+
if (manager) {
|
|
1675
|
+
manager.add({
|
|
1676
|
+
type: "http",
|
|
1677
|
+
category: "http.response",
|
|
1678
|
+
message: `${req.method} ${req.url} - ${res.statusCode}`,
|
|
1679
|
+
level: res.statusCode >= 400 ? "warning" : "info",
|
|
1680
|
+
data: {
|
|
1681
|
+
method: req.method,
|
|
1682
|
+
url: req.url,
|
|
1683
|
+
status_code: res.statusCode,
|
|
1684
|
+
duration_ms: duration,
|
|
1685
|
+
transaction
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function createTrackedServer(handler, options = {}) {
|
|
1694
|
+
return wrapHandler(handler, options);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/index.ts
|
|
1698
|
+
var src_default = ErrorExplorer;
|
|
1699
|
+
|
|
1700
|
+
exports.ErrorExplorer = ErrorExplorer;
|
|
1701
|
+
exports.HmacSigner = HmacSigner;
|
|
1702
|
+
exports.collectOsContext = collectOsContext;
|
|
1703
|
+
exports.collectProcessContext = collectProcessContext;
|
|
1704
|
+
exports.collectRuntimeContext = collectRuntimeContext;
|
|
1705
|
+
exports.createTrackedServer = createTrackedServer;
|
|
1706
|
+
exports.default = src_default;
|
|
1707
|
+
exports.errorHandler = errorHandler;
|
|
1708
|
+
exports.extractRequestContext = extractRequestContext;
|
|
1709
|
+
exports.generateShortId = generateShortId;
|
|
1710
|
+
exports.generateTransactionId = generateTransactionId;
|
|
1711
|
+
exports.generateUuid = generateUuid;
|
|
1712
|
+
exports.getBreadcrumbManager = getBreadcrumbManager;
|
|
1713
|
+
exports.getServerContext = getServerContext;
|
|
1714
|
+
exports.parseStackTrace = parseStackTrace;
|
|
1715
|
+
exports.requestHandler = requestHandler;
|
|
1716
|
+
exports.setServerName = setServerName;
|
|
1717
|
+
exports.setupExpress = setupExpress;
|
|
1718
|
+
exports.wrapHandler = wrapHandler;
|
|
1719
|
+
//# sourceMappingURL=index.cjs.map
|
|
1720
|
+
//# sourceMappingURL=index.cjs.map
|