@oconde/radar 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/PROTOCOL.md +43 -7
- package/README.md +65 -61
- package/dist/bin/radar-sourcemaps.js +244 -0
- package/dist/browser/index.cjs +173 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +30 -0
- package/dist/browser/index.d.ts +30 -0
- package/dist/browser/index.js +152 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/{client-ZpQHpeAu.d.ts → client-BB-to8cr.d.cts} +39 -2
- package/dist/{client-DbNex6tj.d.cts → client-BS798K3S.d.ts} +39 -2
- package/dist/index.cjs +469 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +470 -79
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +469 -78
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +472 -81
- package/dist/nest/index.js.map +1 -1
- package/dist/protocol/index.cjs +16 -3
- package/dist/protocol/index.cjs.map +1 -1
- package/dist/protocol/index.d.cts +27 -2
- package/dist/protocol/index.d.ts +27 -2
- package/dist/protocol/index.js +11 -2
- package/dist/protocol/index.js.map +1 -1
- package/dist/sveltekit/index.cjs +2003 -0
- package/dist/sveltekit/index.cjs.map +1 -0
- package/dist/sveltekit/index.d.cts +28 -0
- package/dist/sveltekit/index.d.ts +28 -0
- package/dist/sveltekit/index.js +1973 -0
- package/dist/sveltekit/index.js.map +1 -0
- package/package.json +39 -4
package/dist/nest/index.js
CHANGED
|
@@ -26,6 +26,33 @@ import { catchError, throwError } from "rxjs";
|
|
|
26
26
|
|
|
27
27
|
// src/context.ts
|
|
28
28
|
import { AsyncLocalStorage } from "async_hooks";
|
|
29
|
+
import { randomUUID } from "crypto";
|
|
30
|
+
|
|
31
|
+
// src/protocol/index.ts
|
|
32
|
+
var INGEST_PATH = "/api/v1/events";
|
|
33
|
+
var CHECKIN_PATH = "/api/v1/checkins/";
|
|
34
|
+
var CHECKIN_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
35
|
+
var CHECKIN_STATUSES = ["in_progress", "ok", "error"];
|
|
36
|
+
var LIMITS = {
|
|
37
|
+
batchBytes: 1e6,
|
|
38
|
+
batchEvents: 500,
|
|
39
|
+
messageLength: 2e3,
|
|
40
|
+
stringLength: 2e3,
|
|
41
|
+
attrsBytes: 16384,
|
|
42
|
+
bodyBytes: 16384,
|
|
43
|
+
headers: 50,
|
|
44
|
+
frames: 50,
|
|
45
|
+
contextFrames: 10,
|
|
46
|
+
contextLines: 5,
|
|
47
|
+
contextLineLength: 300,
|
|
48
|
+
causeDepth: 5,
|
|
49
|
+
depth: 6,
|
|
50
|
+
arrayItems: 50,
|
|
51
|
+
requestIdLength: 128,
|
|
52
|
+
checkInIdLength: 64
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/context.ts
|
|
29
56
|
var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
|
|
30
57
|
function globalState() {
|
|
31
58
|
const holder = globalThis;
|
|
@@ -42,35 +69,19 @@ function currentContext() {
|
|
|
42
69
|
function runWithContext(context, fn) {
|
|
43
70
|
return globalState().storage.run(context, fn);
|
|
44
71
|
}
|
|
72
|
+
var SAFE_REQUEST_ID = /^[\x21-\x7e]+$/;
|
|
73
|
+
function requestIdFrom(incoming) {
|
|
74
|
+
const value = incoming?.trim();
|
|
75
|
+
return value && value.length <= LIMITS.requestIdLength && SAFE_REQUEST_ID.test(value) ? value : randomUUID();
|
|
76
|
+
}
|
|
45
77
|
|
|
46
78
|
// src/client.ts
|
|
47
79
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
48
80
|
import { hostname } from "os";
|
|
49
|
-
import { performance as
|
|
81
|
+
import { performance as performance4 } from "perf_hooks";
|
|
50
82
|
|
|
51
83
|
// src/express.ts
|
|
52
|
-
import {
|
|
53
|
-
import { performance } from "perf_hooks";
|
|
54
|
-
|
|
55
|
-
// src/protocol/index.ts
|
|
56
|
-
var INGEST_PATH = "/api/v1/events";
|
|
57
|
-
var LIMITS = {
|
|
58
|
-
batchBytes: 1e6,
|
|
59
|
-
batchEvents: 500,
|
|
60
|
-
messageLength: 2e3,
|
|
61
|
-
stringLength: 2e3,
|
|
62
|
-
attrsBytes: 16384,
|
|
63
|
-
bodyBytes: 16384,
|
|
64
|
-
headers: 50,
|
|
65
|
-
frames: 50,
|
|
66
|
-
contextFrames: 10,
|
|
67
|
-
contextLines: 5,
|
|
68
|
-
contextLineLength: 300,
|
|
69
|
-
causeDepth: 5,
|
|
70
|
-
depth: 6,
|
|
71
|
-
arrayItems: 50,
|
|
72
|
-
requestIdLength: 128
|
|
73
|
-
};
|
|
84
|
+
import { performance as performance2 } from "perf_hooks";
|
|
74
85
|
|
|
75
86
|
// src/redact.ts
|
|
76
87
|
import { createHash, createHmac, randomBytes } from "crypto";
|
|
@@ -270,7 +281,44 @@ function requestPath(req) {
|
|
|
270
281
|
}
|
|
271
282
|
function requestRoute(req, route) {
|
|
272
283
|
if (route) return route;
|
|
273
|
-
|
|
284
|
+
if (typeof req.route?.path !== "string") return void 0;
|
|
285
|
+
const base = typeof req.baseUrl === "string" ? req.baseUrl.replace(/\/+$/, "") : "";
|
|
286
|
+
if (!base) return req.route.path;
|
|
287
|
+
return req.route.path === "/" ? base : `${base}${req.route.path}`;
|
|
288
|
+
}
|
|
289
|
+
var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
|
|
290
|
+
function pathSegments(value) {
|
|
291
|
+
const segments = value.split("/");
|
|
292
|
+
while (segments.length > 1 && segments[segments.length - 1] === "") segments.pop();
|
|
293
|
+
return segments;
|
|
294
|
+
}
|
|
295
|
+
function decodeSegment(value) {
|
|
296
|
+
try {
|
|
297
|
+
return decodeURIComponent(value);
|
|
298
|
+
} catch {
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function routeParams(req, path) {
|
|
303
|
+
if (typeof req.route?.path !== "string") return {};
|
|
304
|
+
const pattern = pathSegments(`${typeof req.baseUrl === "string" ? req.baseUrl : ""}${req.route.path}`).filter((segment, index) => index > 0 || segment !== "");
|
|
305
|
+
const values = pathSegments(path);
|
|
306
|
+
if (pattern.length > values.length) return {};
|
|
307
|
+
const offset = values.length - pattern.length;
|
|
308
|
+
const result = {};
|
|
309
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
310
|
+
const value = values[offset + index];
|
|
311
|
+
const param = ROUTE_PARAM.exec(pattern[index]);
|
|
312
|
+
if (!param) {
|
|
313
|
+
if (pattern[index] !== value) return {};
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (value) result[param[1]] = decodeSegment(value);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
function pathParams(req, path) {
|
|
321
|
+
return { ...routeParams(req, path), ...req.params ?? {} };
|
|
274
322
|
}
|
|
275
323
|
function redactPath(path, params, mode) {
|
|
276
324
|
if (mode === "none" || !params) return path;
|
|
@@ -287,7 +335,7 @@ function safeUrl(req, mode) {
|
|
|
287
335
|
const queryStart = url.indexOf("?");
|
|
288
336
|
const path = queryStart >= 0 ? url.slice(0, queryStart) : url;
|
|
289
337
|
const query = queryStart >= 0 ? url.slice(queryStart) : "";
|
|
290
|
-
return redactUrl(redactPath(path, req
|
|
338
|
+
return redactUrl(redactPath(path, pathParams(req, path), mode) + query, mode);
|
|
291
339
|
}
|
|
292
340
|
function redactUrl(url, mode) {
|
|
293
341
|
const queryStart = url.indexOf("?");
|
|
@@ -355,9 +403,8 @@ function createMiddleware(logger) {
|
|
|
355
403
|
return (req, res, next) => {
|
|
356
404
|
let context;
|
|
357
405
|
try {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
context = { requestId, startedAt: performance.now(), req };
|
|
406
|
+
const requestId = requestIdFrom(headerValue(req.headers["x-request-id"]));
|
|
407
|
+
context = { requestId, startedAt: performance2.now(), req };
|
|
361
408
|
res.setHeader("x-request-id", requestId);
|
|
362
409
|
if (logger.shouldLogRequest(req)) res.once("finish", () => logger.logHttpRequest(context, res.statusCode));
|
|
363
410
|
} catch {
|
|
@@ -371,6 +418,12 @@ function createMiddleware(logger) {
|
|
|
371
418
|
// src/options.ts
|
|
372
419
|
var DEFAULT_ENDPOINT = "https://radar-ingest.oconde.dev";
|
|
373
420
|
var LEVELS = ["debug", "info", "warn", "error"];
|
|
421
|
+
function resolveTraceFetch(value) {
|
|
422
|
+
if (!value) return { enabled: false, propagateTo: [] };
|
|
423
|
+
if (value === true) return { enabled: true, propagateTo: [] };
|
|
424
|
+
const targets2 = Array.isArray(value.propagateTo) ? value.propagateTo : [];
|
|
425
|
+
return { enabled: true, propagateTo: targets2.filter((target) => typeof target === "string" ? target.trim() !== "" : target instanceof RegExp) };
|
|
426
|
+
}
|
|
374
427
|
function resolveOptions(options = {}, env = process.env) {
|
|
375
428
|
const key = options.key?.trim() || void 0;
|
|
376
429
|
return {
|
|
@@ -386,6 +439,7 @@ function resolveOptions(options = {}, env = process.env) {
|
|
|
386
439
|
logRequests: options.logRequests ?? true,
|
|
387
440
|
ignorePaths: options.ignorePaths ?? ["/healthz", "/health"],
|
|
388
441
|
captureUnhandled: options.captureUnhandled ?? true,
|
|
442
|
+
traceFetch: resolveTraceFetch(options.traceFetch),
|
|
389
443
|
debug: options.debug ?? false
|
|
390
444
|
};
|
|
391
445
|
}
|
|
@@ -447,16 +501,144 @@ import { readFile, stat } from "fs/promises";
|
|
|
447
501
|
import { SourceMap } from "module";
|
|
448
502
|
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
449
503
|
import { fileURLToPath } from "url";
|
|
504
|
+
|
|
505
|
+
// src/function-name.ts
|
|
506
|
+
var IDENTIFIER = "[A-Za-z_$][\\w$]*";
|
|
507
|
+
var MAX_LINES_UP = 400;
|
|
508
|
+
var MAX_HEADER_LINES = 6;
|
|
509
|
+
var MAX_NAME_LENGTH = 100;
|
|
510
|
+
var MAX_COMMENT_LINES = 40;
|
|
511
|
+
var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
|
|
512
|
+
var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
|
|
513
|
+
var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
|
|
514
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "await", "else", "do", "try"]);
|
|
515
|
+
var NAME_PATTERNS = [
|
|
516
|
+
new RegExp(`\\bfunction\\s*\\*?\\s*(${IDENTIFIER})\\s*\\(`),
|
|
517
|
+
new RegExp(`(${IDENTIFIER})\\s*[:=]\\s*(?:async\\s+)?function\\b`),
|
|
518
|
+
new RegExp(`(${IDENTIFIER})\\s*(?::\\s*[^=]+)?=\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`),
|
|
519
|
+
new RegExp(`(?:^|[{,(]\\s*)(${IDENTIFIER})\\s*:\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`)
|
|
520
|
+
];
|
|
521
|
+
var METHOD_PATTERN = new RegExp(`^\\s*(?:(?:export|default|public|private|protected|static|async|override|readonly|get|set)\\s+)*\\*?\\s*(${IDENTIFIER})\\s*(?:<[^>]*>)?\\s*\\([^]*\\)\\s*(?::\\s*[^{]+)?$`);
|
|
522
|
+
var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
|
|
523
|
+
var STRING_LITERAL = /(["'`])(?:\\.|(?!\1)[^\\])*\1/g;
|
|
524
|
+
function lineCommentAt(text, from) {
|
|
525
|
+
for (let index = text.indexOf("//", from); index >= 0; index = text.indexOf("//", index + 1)) {
|
|
526
|
+
if (index === 0 || text[index - 1] !== ":") return index;
|
|
527
|
+
}
|
|
528
|
+
return -1;
|
|
529
|
+
}
|
|
530
|
+
function blockCommentAt(text, from) {
|
|
531
|
+
for (let index = text.indexOf("/*", from); index >= 0; index = text.indexOf("/*", index + 1)) {
|
|
532
|
+
if (index === 0 || text[index - 1] !== "\\") return index;
|
|
533
|
+
}
|
|
534
|
+
return -1;
|
|
535
|
+
}
|
|
536
|
+
function stripComments(text, insideComment, closesLater) {
|
|
537
|
+
let code = "";
|
|
538
|
+
let position = 0;
|
|
539
|
+
let inside = insideComment;
|
|
540
|
+
while (position < text.length) {
|
|
541
|
+
if (inside) {
|
|
542
|
+
const end = text.indexOf("*/", position);
|
|
543
|
+
if (end < 0) return { code, insideComment: true };
|
|
544
|
+
inside = false;
|
|
545
|
+
position = end + 2;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
const block = blockCommentAt(text, position);
|
|
549
|
+
const line = lineCommentAt(text, position);
|
|
550
|
+
if (line >= 0 && (block < 0 || line < block)) return { code: code + text.slice(position, line), insideComment: false };
|
|
551
|
+
if (block < 0) return { code: code + text.slice(position), insideComment: false };
|
|
552
|
+
if (text.indexOf("*/", block + 2) < 0 && !closesLater()) {
|
|
553
|
+
code += text.slice(position, block + 2);
|
|
554
|
+
position = block + 2;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
code += text.slice(position, block);
|
|
558
|
+
inside = true;
|
|
559
|
+
position = block + 2;
|
|
560
|
+
}
|
|
561
|
+
return { code, insideComment: inside };
|
|
562
|
+
}
|
|
563
|
+
function codeLines(lines, from, start, column) {
|
|
564
|
+
const plain = lines.slice(from, start + 1).map((line) => line.replace(STRING_LITERAL, '""'));
|
|
565
|
+
const closesWithin = (index) => plain.slice(index + 1, index + 1 + MAX_COMMENT_LINES).some((line) => line.includes("*/"));
|
|
566
|
+
const result = [];
|
|
567
|
+
let insideComment = false;
|
|
568
|
+
for (let index = 0; index < plain.length; index++) {
|
|
569
|
+
const text = index === plain.length - 1 && column ? (lines[start] ?? "").slice(0, Math.max(0, column - 1)).replace(STRING_LITERAL, '""') : plain[index] ?? "";
|
|
570
|
+
const stripped = stripComments(text, insideComment, () => closesWithin(index));
|
|
571
|
+
insideComment = stripped.insideComment;
|
|
572
|
+
result.push(stripped.code);
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
function parenBalance(text) {
|
|
577
|
+
let balance = 0;
|
|
578
|
+
for (const char of text) {
|
|
579
|
+
if (char === "(") balance += 1;
|
|
580
|
+
else if (char === ")") balance -= 1;
|
|
581
|
+
}
|
|
582
|
+
return balance;
|
|
583
|
+
}
|
|
584
|
+
function headerOf(code, index, prefix) {
|
|
585
|
+
let header = prefix;
|
|
586
|
+
for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
|
|
587
|
+
if (header.trim() !== "" && parenBalance(header) >= 0) break;
|
|
588
|
+
header = `${code[previous] ?? ""} ${header}`;
|
|
589
|
+
}
|
|
590
|
+
return header;
|
|
591
|
+
}
|
|
592
|
+
function nameIn(header) {
|
|
593
|
+
const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
|
|
594
|
+
for (const pattern of patterns) {
|
|
595
|
+
const name = pattern.exec(header)?.[1];
|
|
596
|
+
if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
|
|
597
|
+
}
|
|
598
|
+
return void 0;
|
|
599
|
+
}
|
|
600
|
+
function enclosingFunctionName(lines, line, column) {
|
|
601
|
+
const start = line - 1;
|
|
602
|
+
if (start < 0 || start >= lines.length) return void 0;
|
|
603
|
+
const from = Math.max(0, start - MAX_LINES_UP - MAX_HEADER_LINES);
|
|
604
|
+
const cleaned = codeLines(lines, from, start, column);
|
|
605
|
+
let depth = 0;
|
|
606
|
+
for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
|
|
607
|
+
if (index < start && MARKUP_BOUNDARY.test(lines[index] ?? "")) return void 0;
|
|
608
|
+
const code = cleaned[index - from] ?? "";
|
|
609
|
+
for (let position = code.length - 1; position >= 0; position--) {
|
|
610
|
+
const char = code[position];
|
|
611
|
+
if (char === "}") {
|
|
612
|
+
depth += 1;
|
|
613
|
+
} else if (char === "{") {
|
|
614
|
+
if (depth > 0) {
|
|
615
|
+
depth -= 1;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const header = headerOf(cleaned, index - from, code.slice(0, position));
|
|
619
|
+
if (CLASS_BODY.test(header)) return void 0;
|
|
620
|
+
if (CONTROL_BLOCK.test(header)) continue;
|
|
621
|
+
const name = nameIn(header);
|
|
622
|
+
if (name) return name;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return void 0;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/source.ts
|
|
450
630
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
451
631
|
var MAX_CACHED_FILES = 50;
|
|
452
632
|
var MAPPING_URL = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
|
|
453
633
|
var cache = /* @__PURE__ */ new Map();
|
|
454
|
-
async function enrichFrames(frames, cwd = process.cwd()) {
|
|
634
|
+
async function enrichFrames(frames, cwd = process.cwd(), options = {}) {
|
|
455
635
|
const result = [];
|
|
456
636
|
let withContext = 0;
|
|
457
637
|
for (const frame of frames) {
|
|
458
638
|
try {
|
|
459
|
-
const { frame:
|
|
639
|
+
const { frame: located, lines, original } = await mapFrame(frame, options.mapLocator);
|
|
640
|
+
const named = options.inferFunctionNames && original && !located.fn && lines && located.line ? withInferredName(located, lines) : located;
|
|
641
|
+
const mapped = named.inApp && isLibraryPath(named.file) ? { ...named, inApp: false } : named;
|
|
460
642
|
let next = mapped;
|
|
461
643
|
if (mapped.inApp && lines && mapped.line && withContext < LIMITS.contextFrames) {
|
|
462
644
|
const context = contextFor(lines, mapped.line);
|
|
@@ -472,29 +654,36 @@ async function enrichFrames(frames, cwd = process.cwd()) {
|
|
|
472
654
|
}
|
|
473
655
|
return result;
|
|
474
656
|
}
|
|
475
|
-
async function enrichException(exception, cwd = process.cwd()) {
|
|
476
|
-
const frames = await enrichFrames(exception.frames, cwd);
|
|
657
|
+
async function enrichException(exception, cwd = process.cwd(), options = {}) {
|
|
658
|
+
const frames = await enrichFrames(exception.frames, cwd, options);
|
|
477
659
|
if (!exception.cause) return { ...exception, frames };
|
|
478
|
-
return { ...exception, frames, cause: await enrichException(exception.cause, cwd) };
|
|
660
|
+
return { ...exception, frames, cause: await enrichException(exception.cause, cwd, options) };
|
|
661
|
+
}
|
|
662
|
+
function withInferredName(frame, lines) {
|
|
663
|
+
const fn = enclosingFunctionName(lines, frame.line ?? 0, frame.col);
|
|
664
|
+
return fn ? { ...frame, fn } : frame;
|
|
665
|
+
}
|
|
666
|
+
function isLibraryPath(file) {
|
|
667
|
+
return file.includes("/node_modules/") || file.includes("\\node_modules\\");
|
|
479
668
|
}
|
|
480
669
|
function relativize(file, cwd) {
|
|
481
670
|
if (!isAbsolute(file)) return file;
|
|
482
671
|
const path = relative(cwd, file);
|
|
483
672
|
return path.startsWith("..") || isAbsolute(path) ? file : path.split(sep).join("/");
|
|
484
673
|
}
|
|
485
|
-
async function mapFrame(frame) {
|
|
486
|
-
if (!frame.line || !isAbsolute(frame.file)) return { frame, lines: null };
|
|
487
|
-
const file = await loadFile(frame.file);
|
|
488
|
-
if (!file) return { frame, lines: null };
|
|
489
|
-
if (!file.map) return { frame, lines: file.lines };
|
|
674
|
+
async function mapFrame(frame, locator) {
|
|
675
|
+
if (!frame.line || !isAbsolute(frame.file)) return { frame, lines: null, original: false };
|
|
676
|
+
const file = await loadFile(frame.file, locator);
|
|
677
|
+
if (!file) return { frame, lines: null, original: false };
|
|
678
|
+
if (!file.map) return { frame, lines: file.lines, original: false };
|
|
490
679
|
const entry = file.map.map.findEntry(frame.line - 1, (frame.col ?? 1) - 1);
|
|
491
|
-
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines };
|
|
680
|
+
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines, original: false };
|
|
492
681
|
const originalPath = resolveSource(file.map, entry.originalSource);
|
|
493
682
|
const mapped = { ...frame, file: originalPath, line: entry.originalLine + 1, col: (entry.originalColumn ?? 0) + 1 };
|
|
494
683
|
const lines = embeddedLines(file.map, file.map.sources.indexOf(entry.originalSource));
|
|
495
|
-
if (lines) return { frame: mapped, lines };
|
|
684
|
+
if (lines) return { frame: mapped, lines, original: true };
|
|
496
685
|
const original = await loadFile(originalPath);
|
|
497
|
-
return { frame: mapped, lines: original?.lines ?? null };
|
|
686
|
+
return { frame: mapped, lines: original?.lines ?? null, original: true };
|
|
498
687
|
}
|
|
499
688
|
function embeddedLines(map, index) {
|
|
500
689
|
if (index < 0) return null;
|
|
@@ -526,26 +715,27 @@ function resolveSource(map, source) {
|
|
|
526
715
|
}
|
|
527
716
|
return isAbsolute(source) ? source : resolve(map.baseDir, source);
|
|
528
717
|
}
|
|
529
|
-
function loadFile(path) {
|
|
530
|
-
const
|
|
718
|
+
function loadFile(path, locator) {
|
|
719
|
+
const key = locator ? `${path}\0located` : path;
|
|
720
|
+
const cached = cache.get(key);
|
|
531
721
|
if (cached) {
|
|
532
|
-
cache.delete(
|
|
533
|
-
cache.set(
|
|
722
|
+
cache.delete(key);
|
|
723
|
+
cache.set(key, cached);
|
|
534
724
|
return cached;
|
|
535
725
|
}
|
|
536
|
-
const pending = readSource(path).catch(() => null);
|
|
537
|
-
cache.set(
|
|
726
|
+
const pending = readSource(path, locator).catch(() => null);
|
|
727
|
+
cache.set(key, pending);
|
|
538
728
|
if (cache.size > MAX_CACHED_FILES) {
|
|
539
729
|
const oldest = cache.keys().next().value;
|
|
540
730
|
if (oldest !== void 0) cache.delete(oldest);
|
|
541
731
|
}
|
|
542
732
|
return pending;
|
|
543
733
|
}
|
|
544
|
-
async function readSource(path) {
|
|
734
|
+
async function readSource(path, locator) {
|
|
545
735
|
const text = await readText(path);
|
|
546
736
|
if (text === null) return null;
|
|
547
737
|
const lines = text.split(/\r?\n/);
|
|
548
|
-
return { lines, map: await loadMap(path, lines) };
|
|
738
|
+
return { lines, map: await loadMap(path, lines, locator) };
|
|
549
739
|
}
|
|
550
740
|
async function readText(path) {
|
|
551
741
|
try {
|
|
@@ -556,7 +746,7 @@ async function readText(path) {
|
|
|
556
746
|
return null;
|
|
557
747
|
}
|
|
558
748
|
}
|
|
559
|
-
async function loadMap(path, lines) {
|
|
749
|
+
async function loadMap(path, lines, locator) {
|
|
560
750
|
let url;
|
|
561
751
|
for (let index = lines.length - 1; index >= 0 && index >= lines.length - 5; index--) {
|
|
562
752
|
const match = MAPPING_URL.exec(lines[index] ?? "");
|
|
@@ -565,10 +755,15 @@ async function loadMap(path, lines) {
|
|
|
565
755
|
break;
|
|
566
756
|
}
|
|
567
757
|
}
|
|
568
|
-
if (!url) return null;
|
|
569
758
|
let raw;
|
|
570
759
|
let mapDir;
|
|
571
|
-
|
|
760
|
+
const located = url ? null : locator ? locator(path) : `${path}.map`;
|
|
761
|
+
if (located) {
|
|
762
|
+
raw = await readText(located);
|
|
763
|
+
mapDir = dirname(located);
|
|
764
|
+
} else if (!url) {
|
|
765
|
+
return null;
|
|
766
|
+
} else if (url.startsWith("data:")) {
|
|
572
767
|
const comma = url.indexOf(",");
|
|
573
768
|
const encoded = url.slice(comma + 1);
|
|
574
769
|
raw = url.slice(0, comma).endsWith(";base64") ? Buffer.from(encoded, "base64").toString("utf8") : decodeURIComponent(encoded);
|
|
@@ -597,12 +792,13 @@ async function loadMap(path, lines) {
|
|
|
597
792
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
598
793
|
var WITH_FUNCTION = /^at (?:async )?(.+?) \((.+)\)$/;
|
|
599
794
|
var LOCATION = /^(.+):(\d+):(\d+)$/;
|
|
795
|
+
var MAX_FRAME_LINE = 1e3;
|
|
600
796
|
function parseStack(stack) {
|
|
601
797
|
if (!stack) return [];
|
|
602
798
|
const frames = [];
|
|
603
799
|
for (const raw of stack.split("\n")) {
|
|
604
800
|
const line = raw.trim();
|
|
605
|
-
if (!line.startsWith("at ")) continue;
|
|
801
|
+
if (line.length > MAX_FRAME_LINE || !line.startsWith("at ")) continue;
|
|
606
802
|
const withFunction = WITH_FUNCTION.exec(line);
|
|
607
803
|
const fn = withFunction?.[1];
|
|
608
804
|
const location = withFunction ? withFunction[2] ?? "" : line.replace(/^at (?:async )?/, "");
|
|
@@ -661,20 +857,110 @@ function safeStringify(value) {
|
|
|
661
857
|
}
|
|
662
858
|
}
|
|
663
859
|
|
|
664
|
-
// src/transport.ts
|
|
665
|
-
import { promisify } from "util";
|
|
666
|
-
import { gzip } from "zlib";
|
|
667
|
-
|
|
668
860
|
// src/util.ts
|
|
669
|
-
import { performance as
|
|
861
|
+
import { performance as performance3 } from "perf_hooks";
|
|
670
862
|
function describeError(error) {
|
|
671
863
|
return error instanceof Error ? error.message : String(error);
|
|
672
864
|
}
|
|
673
865
|
function elapsedSince(startedAt) {
|
|
674
|
-
return Math.round(
|
|
866
|
+
return Math.round(performance3.now() - startedAt);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/trace-fetch.ts
|
|
870
|
+
var WRAPPED = /* @__PURE__ */ Symbol.for("@oconde/radar.fetch");
|
|
871
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
872
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
873
|
+
var TELEGRAM_BOT = /^bot\d{5,}:[\w-]{20,}$/;
|
|
874
|
+
var TOKEN_LIKE = /^[\w\-.~+=]{20,}$/;
|
|
875
|
+
function secretSegment(segment) {
|
|
876
|
+
if (UUID.test(segment)) return false;
|
|
877
|
+
if (TELEGRAM_BOT.test(segment) || looksLikeCredential(segment)) return true;
|
|
878
|
+
return TOKEN_LIKE.test(segment) && /[A-Za-z]/.test(segment) && /\d/.test(segment);
|
|
879
|
+
}
|
|
880
|
+
function outboundPath(pathname, mode) {
|
|
881
|
+
if (mode === "none") return pathname;
|
|
882
|
+
return pathname.split("/").map((segment) => {
|
|
883
|
+
let decoded = segment;
|
|
884
|
+
try {
|
|
885
|
+
decoded = decodeURIComponent(segment);
|
|
886
|
+
} catch {
|
|
887
|
+
decoded = segment;
|
|
888
|
+
}
|
|
889
|
+
return secretSegment(decoded) ? maskValue(decoded, false) : segment;
|
|
890
|
+
}).join("/");
|
|
891
|
+
}
|
|
892
|
+
function failureOf(error) {
|
|
893
|
+
const message = describeError(error);
|
|
894
|
+
const cause = error instanceof Error ? error.cause : void 0;
|
|
895
|
+
const detail = typeof cause?.code === "string" ? cause.code : typeof cause?.message === "string" ? cause.message : "";
|
|
896
|
+
return detail && !message.includes(detail) ? `${message} (${detail})` : message;
|
|
897
|
+
}
|
|
898
|
+
function urlOf(input) {
|
|
899
|
+
try {
|
|
900
|
+
if (typeof input === "string") return new URL(input);
|
|
901
|
+
if (input instanceof URL) return input;
|
|
902
|
+
return new URL(input.url);
|
|
903
|
+
} catch {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function originOf(value) {
|
|
908
|
+
try {
|
|
909
|
+
return new URL(value).origin;
|
|
910
|
+
} catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function targets(url, target) {
|
|
915
|
+
if (target instanceof RegExp) return target.test(url.host);
|
|
916
|
+
const wanted = target.trim().toLowerCase();
|
|
917
|
+
if (wanted.startsWith(".")) return url.hostname.toLowerCase().endsWith(wanted);
|
|
918
|
+
return url.host.toLowerCase() === wanted || url.hostname.toLowerCase() === wanted;
|
|
919
|
+
}
|
|
920
|
+
function isPlainObject(value) {
|
|
921
|
+
if (value === null || typeof value !== "object") return false;
|
|
922
|
+
const prototype = Object.getPrototypeOf(value);
|
|
923
|
+
return prototype === Object.prototype || prototype === null;
|
|
924
|
+
}
|
|
925
|
+
function withRequestId(input, init, requestId) {
|
|
926
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
927
|
+
if (headers.has(REQUEST_ID_HEADER)) return [input, init];
|
|
928
|
+
headers.set(REQUEST_ID_HEADER, requestId);
|
|
929
|
+
if (init === void 0 || isPlainObject(init)) return [input, { ...init, headers }];
|
|
930
|
+
return [new Request(input, init), { headers }];
|
|
931
|
+
}
|
|
932
|
+
function traceFetch(config) {
|
|
933
|
+
const current = globalThis.fetch;
|
|
934
|
+
if (typeof current !== "function" || current[WRAPPED]) return () => void 0;
|
|
935
|
+
const original = current;
|
|
936
|
+
const radarOrigin = originOf(config.endpoint);
|
|
937
|
+
const traced = async (input, init) => {
|
|
938
|
+
const url = urlOf(input);
|
|
939
|
+
if (!url || url.origin === radarOrigin || url.protocol !== "http:" && url.protocol !== "https:") return original(input, init);
|
|
940
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
941
|
+
const context = currentContext();
|
|
942
|
+
const [nextInput, nextInit] = context && config.propagateTo.some((target) => targets(url, target)) ? withRequestId(input, init, context.requestId) : [input, init];
|
|
943
|
+
const attrs = { method, host: url.host, path: outboundPath(url.pathname, config.redact) };
|
|
944
|
+
const started = performance.now();
|
|
945
|
+
try {
|
|
946
|
+
const response = await original(nextInput, nextInit);
|
|
947
|
+
config.record(response.status >= 500 ? "error" : response.status >= 400 ? "warn" : "info", { ...attrs, status: response.status, durationMs: elapsedSince(started) });
|
|
948
|
+
return response;
|
|
949
|
+
} catch (error) {
|
|
950
|
+
config.record("error", { ...attrs, error: failureOf(error), durationMs: elapsedSince(started) });
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
Object.defineProperty(traced, WRAPPED, { value: original });
|
|
955
|
+
globalThis.fetch = traced;
|
|
956
|
+
return () => {
|
|
957
|
+
if (globalThis.fetch === traced) globalThis.fetch = original;
|
|
958
|
+
};
|
|
675
959
|
}
|
|
676
960
|
|
|
677
961
|
// src/transport.ts
|
|
962
|
+
import { promisify } from "util";
|
|
963
|
+
import { gzip } from "zlib";
|
|
678
964
|
var gzipAsync = promisify(gzip);
|
|
679
965
|
var MAX_BATCH_BYTES = 9e5;
|
|
680
966
|
var GZIP_THRESHOLD_BYTES = 8192;
|
|
@@ -697,6 +983,7 @@ var Transport = class {
|
|
|
697
983
|
inFlight = null;
|
|
698
984
|
nextAttemptAt = 0;
|
|
699
985
|
backoffMs = 0;
|
|
986
|
+
unauthorizedUntil = 0;
|
|
700
987
|
stopped = false;
|
|
701
988
|
warned = /* @__PURE__ */ new Set();
|
|
702
989
|
timer;
|
|
@@ -786,10 +1073,8 @@ var Transport = class {
|
|
|
786
1073
|
this.nextAttemptAt = Date.now() + Math.max(this.backoffMs, outcome.retryAfterMs ?? 0);
|
|
787
1074
|
return false;
|
|
788
1075
|
case "unauthorized":
|
|
789
|
-
this.droppedCount += events.length
|
|
790
|
-
this.
|
|
791
|
-
this.nextAttemptAt = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
792
|
-
this.warn("unauthorized", `chave do Radar recusada (${outcome.status}); envios pausados por 10 minutos`, true);
|
|
1076
|
+
this.droppedCount += events.length;
|
|
1077
|
+
this.pauseUnauthorized(outcome.status);
|
|
793
1078
|
return false;
|
|
794
1079
|
case "rejected":
|
|
795
1080
|
this.droppedCount += events.length;
|
|
@@ -797,6 +1082,47 @@ var Transport = class {
|
|
|
797
1082
|
return true;
|
|
798
1083
|
}
|
|
799
1084
|
}
|
|
1085
|
+
async checkIn(slug, payload) {
|
|
1086
|
+
if (this.stopped || !this.config.checkInBaseUrl || Date.now() < this.unauthorizedUntil) return;
|
|
1087
|
+
let response;
|
|
1088
|
+
try {
|
|
1089
|
+
response = await fetch(this.config.checkInBaseUrl + encodeURIComponent(slug), {
|
|
1090
|
+
method: "POST",
|
|
1091
|
+
headers: this.headers(),
|
|
1092
|
+
body: JSON.stringify(payload),
|
|
1093
|
+
redirect: "error",
|
|
1094
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
|
|
1095
|
+
});
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
this.warn("checkin_network", `check-in no Radar falhou: ${describeError(error)}`, false);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
await response.arrayBuffer().catch(() => void 0);
|
|
1101
|
+
if (response.ok) return;
|
|
1102
|
+
if (response.status === 401 || response.status === 403) {
|
|
1103
|
+
this.pauseUnauthorized(response.status);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (response.status === 404) {
|
|
1107
|
+
this.warn(`checkin_unknown_${slug}`, `tarefa agendada "${slug}" n\xE3o existe no projeto da chave; crie o monitor com esse slug no Radar`, true);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
this.warn(`checkin_rejected_${response.status}`, `check-in recusado pelo Radar (${response.status})`, false);
|
|
1111
|
+
}
|
|
1112
|
+
pauseUnauthorized(status) {
|
|
1113
|
+
this.droppedCount += this.queue.length;
|
|
1114
|
+
this.queue = [];
|
|
1115
|
+
this.unauthorizedUntil = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
1116
|
+
this.nextAttemptAt = Math.max(this.nextAttemptAt, this.unauthorizedUntil);
|
|
1117
|
+
this.warn("unauthorized", `chave do Radar recusada (${status}); envios pausados por 10 minutos`, true);
|
|
1118
|
+
}
|
|
1119
|
+
headers() {
|
|
1120
|
+
return {
|
|
1121
|
+
"content-type": "application/json",
|
|
1122
|
+
authorization: `Bearer ${this.config.key}`,
|
|
1123
|
+
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
800
1126
|
requeue(events) {
|
|
801
1127
|
this.queue.unshift(...events);
|
|
802
1128
|
const overflow = this.queue.length - this.maxQueue;
|
|
@@ -808,11 +1134,7 @@ var Transport = class {
|
|
|
808
1134
|
async post(batch) {
|
|
809
1135
|
const json = JSON.stringify(batch);
|
|
810
1136
|
const compress = Buffer.byteLength(json) > GZIP_THRESHOLD_BYTES;
|
|
811
|
-
const headers =
|
|
812
|
-
"content-type": "application/json",
|
|
813
|
-
authorization: `Bearer ${this.config.key}`,
|
|
814
|
-
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
815
|
-
};
|
|
1137
|
+
const headers = this.headers();
|
|
816
1138
|
if (compress) headers["content-encoding"] = "gzip";
|
|
817
1139
|
let response;
|
|
818
1140
|
try {
|
|
@@ -866,15 +1188,19 @@ function withTimeout(promise, ms) {
|
|
|
866
1188
|
|
|
867
1189
|
// src/version.ts
|
|
868
1190
|
var SDK_NAME = "@oconde/radar";
|
|
869
|
-
var SDK_VERSION = true ? "0.
|
|
1191
|
+
var SDK_VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
870
1192
|
|
|
871
1193
|
// src/client.ts
|
|
1194
|
+
var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
|
|
1195
|
+
var MAX_CHECK_IN_ENVIRONMENT = 64;
|
|
1196
|
+
var MAX_CHECK_IN_RELEASE = 128;
|
|
872
1197
|
var RadarClient = class {
|
|
873
1198
|
options = resolveOptions({}, {});
|
|
874
1199
|
transport = null;
|
|
875
1200
|
initialized = false;
|
|
876
1201
|
closed = false;
|
|
877
1202
|
captured = /* @__PURE__ */ new WeakSet();
|
|
1203
|
+
restoreFetch = null;
|
|
878
1204
|
get isEnabled() {
|
|
879
1205
|
return this.transport !== null && !this.closed;
|
|
880
1206
|
}
|
|
@@ -897,10 +1223,19 @@ var RadarClient = class {
|
|
|
897
1223
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
898
1224
|
environment: this.options.environment,
|
|
899
1225
|
...this.options.release ? { release: this.options.release } : {},
|
|
1226
|
+
checkInBaseUrl: this.options.endpoint + CHECKIN_PATH,
|
|
900
1227
|
onWarning: (message, always) => always ? this.writeWarning(message) : this.debugWarn(message)
|
|
901
1228
|
});
|
|
902
1229
|
}
|
|
903
1230
|
if (this.options.captureUnhandled && (this.transport || this.options.console)) installProcessHandlers(this);
|
|
1231
|
+
if (this.options.traceFetch.enabled && (this.transport || this.options.console)) {
|
|
1232
|
+
this.restoreFetch = traceFetch({
|
|
1233
|
+
endpoint: this.options.endpoint,
|
|
1234
|
+
propagateTo: this.options.traceFetch.propagateTo,
|
|
1235
|
+
redact: this.options.redact,
|
|
1236
|
+
record: (level, attrs) => this.safely(() => this.emitLog(level, "http.client", attrs))
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
904
1239
|
} catch (error) {
|
|
905
1240
|
this.debugWarn(`radar.init falhou: ${describeError(error)}`);
|
|
906
1241
|
}
|
|
@@ -944,7 +1279,7 @@ var RadarClient = class {
|
|
|
944
1279
|
}
|
|
945
1280
|
async track(name, fn, data) {
|
|
946
1281
|
const execute = async () => {
|
|
947
|
-
const started =
|
|
1282
|
+
const started = performance4.now();
|
|
948
1283
|
try {
|
|
949
1284
|
const result = await fn();
|
|
950
1285
|
this.info(name, { ...data, durationMs: elapsedSince(started), ok: true });
|
|
@@ -956,7 +1291,52 @@ var RadarClient = class {
|
|
|
956
1291
|
}
|
|
957
1292
|
};
|
|
958
1293
|
if (currentContext()) return execute();
|
|
959
|
-
return runWithContext({ requestId: randomUUID2(), startedAt:
|
|
1294
|
+
return runWithContext({ requestId: randomUUID2(), startedAt: performance4.now() }, execute);
|
|
1295
|
+
}
|
|
1296
|
+
withContext(fn, options = {}) {
|
|
1297
|
+
return runWithContext({ requestId: requestIdFrom(options.requestId), startedAt: performance4.now() }, fn);
|
|
1298
|
+
}
|
|
1299
|
+
async checkIn(slug, status, options = {}) {
|
|
1300
|
+
try {
|
|
1301
|
+
if (!this.transport || this.closed) return;
|
|
1302
|
+
if (typeof slug !== "string" || !CHECKIN_SLUG.test(slug)) {
|
|
1303
|
+
this.debugWarn(`slug de tarefa agendada inv\xE1lido: ${String(slug).slice(0, 80)}`);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (!CHECKIN_STATUSES.includes(status)) return;
|
|
1307
|
+
const checkInId = typeof options.checkInId === "string" && options.checkInId ? options.checkInId.slice(0, LIMITS.checkInIdLength) : void 0;
|
|
1308
|
+
const durationMs = typeof options.durationMs === "number" && Number.isFinite(options.durationMs) && options.durationMs >= 0 ? Math.min(Math.round(options.durationMs), MAX_CHECK_IN_DURATION_MS) : void 0;
|
|
1309
|
+
const environment = this.options.environment.slice(0, MAX_CHECK_IN_ENVIRONMENT) || "production";
|
|
1310
|
+
const release = this.options.release?.slice(0, MAX_CHECK_IN_RELEASE);
|
|
1311
|
+
const payload = {
|
|
1312
|
+
status,
|
|
1313
|
+
...checkInId ? { checkInId } : {},
|
|
1314
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1315
|
+
environment,
|
|
1316
|
+
...release ? { release } : {}
|
|
1317
|
+
};
|
|
1318
|
+
await this.transport.checkIn(slug, payload);
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
this.debugWarn(`check-in falhou: ${describeError(error)}`);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
async cron(slug, fn) {
|
|
1324
|
+
const checkInId = randomUUID2();
|
|
1325
|
+
const started = performance4.now();
|
|
1326
|
+
const execute = async () => {
|
|
1327
|
+
await this.checkIn(slug, "in_progress", { checkInId });
|
|
1328
|
+
try {
|
|
1329
|
+
const result = await fn();
|
|
1330
|
+
await this.checkIn(slug, "ok", { checkInId, durationMs: elapsedSince(started) });
|
|
1331
|
+
return result;
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
this.captureError(error, { cron: slug });
|
|
1334
|
+
await this.checkIn(slug, "error", { checkInId, durationMs: elapsedSince(started) });
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
if (currentContext()) return execute();
|
|
1339
|
+
return this.withContext(execute);
|
|
960
1340
|
}
|
|
961
1341
|
middleware() {
|
|
962
1342
|
return createMiddleware(this);
|
|
@@ -975,7 +1355,7 @@ var RadarClient = class {
|
|
|
975
1355
|
() => this.emitLog(level, "http.request", {
|
|
976
1356
|
method: (req.method ?? "GET").toUpperCase(),
|
|
977
1357
|
...route ? { route } : {},
|
|
978
|
-
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), req
|
|
1358
|
+
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
|
|
979
1359
|
status,
|
|
980
1360
|
durationMs: elapsedSince(context.startedAt)
|
|
981
1361
|
})
|
|
@@ -992,6 +1372,8 @@ var RadarClient = class {
|
|
|
992
1372
|
async close(timeoutMs = 2e3) {
|
|
993
1373
|
if (this.closed) return;
|
|
994
1374
|
await this.flush(timeoutMs);
|
|
1375
|
+
this.restoreFetch?.();
|
|
1376
|
+
this.restoreFetch = null;
|
|
995
1377
|
this.closed = true;
|
|
996
1378
|
this.transport?.stop();
|
|
997
1379
|
}
|
|
@@ -1012,31 +1394,40 @@ var RadarClient = class {
|
|
|
1012
1394
|
this.print(event);
|
|
1013
1395
|
this.transport?.enqueue(event);
|
|
1014
1396
|
}
|
|
1397
|
+
captureException(exception, options = {}) {
|
|
1398
|
+
this.safely(() => this.emitException(exception, options));
|
|
1399
|
+
}
|
|
1015
1400
|
emitError(error, data, level, handled) {
|
|
1016
1401
|
if (this.closed) return;
|
|
1017
1402
|
if (typeof error === "object" && error !== null) {
|
|
1018
1403
|
if (this.captured.has(error)) return;
|
|
1019
1404
|
this.captured.add(error);
|
|
1020
1405
|
}
|
|
1406
|
+
this.emitException(exceptionInfo(error), { level, handled, ...data !== void 0 ? { attrs: data } : {} }, error instanceof Error ? error.stack : void 0);
|
|
1407
|
+
}
|
|
1408
|
+
emitException(exception, options, stack) {
|
|
1409
|
+
if (this.closed) return;
|
|
1021
1410
|
const context = currentContext();
|
|
1022
1411
|
const mode = this.options.redact;
|
|
1412
|
+
const request = options.request === null ? void 0 : options.request ?? (context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0);
|
|
1413
|
+
const runtime = options.runtime === void 0 ? runtimeInfo() : options.runtime;
|
|
1023
1414
|
const event = {
|
|
1024
1415
|
type: "error",
|
|
1025
1416
|
ts: Date.now(),
|
|
1026
|
-
level,
|
|
1027
|
-
handled,
|
|
1028
|
-
exception
|
|
1417
|
+
level: options.level ?? "error",
|
|
1418
|
+
handled: options.handled ?? true,
|
|
1419
|
+
exception,
|
|
1029
1420
|
...context ? { requestId: context.requestId } : {},
|
|
1030
|
-
...
|
|
1421
|
+
...request ? { request } : {},
|
|
1031
1422
|
...context?.user ? { user: context.user } : {},
|
|
1032
|
-
runtime:
|
|
1033
|
-
...
|
|
1423
|
+
...runtime ? { runtime } : {},
|
|
1424
|
+
...options.attrs !== void 0 ? { attrs: prepareAttrs(options.attrs, mode) } : {}
|
|
1034
1425
|
};
|
|
1035
|
-
this.print(event,
|
|
1426
|
+
this.print(event, stack);
|
|
1036
1427
|
if (!this.transport) return;
|
|
1037
1428
|
this.transport.enqueue(
|
|
1038
|
-
() => enrichException(event.exception).then(
|
|
1039
|
-
(
|
|
1429
|
+
() => enrichException(event.exception, process.cwd(), options.enrich).then(
|
|
1430
|
+
(enriched) => ({ ...event, exception: enriched }),
|
|
1040
1431
|
() => event
|
|
1041
1432
|
)
|
|
1042
1433
|
);
|