@oconde/radar 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/PROTOCOL.md +43 -7
- package/README.md +57 -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-CZHtsQAd.d.cts} +28 -2
- package/dist/{client-DbNex6tj.d.cts → client-DbBsXgpj.d.ts} +28 -2
- package/dist/index.cjs +301 -73
- 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 +296 -68
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +301 -73
- 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 +297 -69
- 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 +1840 -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 +1810 -0
- package/dist/sveltekit/index.js.map +1 -0
- package/package.json +39 -4
package/dist/index.cjs
CHANGED
|
@@ -32,29 +32,13 @@ var import_node_perf_hooks3 = require("perf_hooks");
|
|
|
32
32
|
|
|
33
33
|
// src/context.ts
|
|
34
34
|
var import_node_async_hooks = require("async_hooks");
|
|
35
|
-
var
|
|
36
|
-
function globalState() {
|
|
37
|
-
const holder = globalThis;
|
|
38
|
-
let state2 = holder[STATE_KEY];
|
|
39
|
-
if (!state2) {
|
|
40
|
-
state2 = { storage: new import_node_async_hooks.AsyncLocalStorage() };
|
|
41
|
-
holder[STATE_KEY] = state2;
|
|
42
|
-
}
|
|
43
|
-
return state2;
|
|
44
|
-
}
|
|
45
|
-
function currentContext() {
|
|
46
|
-
return globalState().storage.getStore();
|
|
47
|
-
}
|
|
48
|
-
function runWithContext(context, fn) {
|
|
49
|
-
return globalState().storage.run(context, fn);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/express.ts
|
|
53
|
-
var import_node_crypto2 = require("crypto");
|
|
54
|
-
var import_node_perf_hooks = require("perf_hooks");
|
|
35
|
+
var import_node_crypto = require("crypto");
|
|
55
36
|
|
|
56
37
|
// src/protocol/index.ts
|
|
57
38
|
var INGEST_PATH = "/api/v1/events";
|
|
39
|
+
var CHECKIN_PATH = "/api/v1/checkins/";
|
|
40
|
+
var CHECKIN_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
41
|
+
var CHECKIN_STATUSES = ["in_progress", "ok", "error"];
|
|
58
42
|
var LIMITS = {
|
|
59
43
|
batchBytes: 1e6,
|
|
60
44
|
batchEvents: 500,
|
|
@@ -70,11 +54,38 @@ var LIMITS = {
|
|
|
70
54
|
causeDepth: 5,
|
|
71
55
|
depth: 6,
|
|
72
56
|
arrayItems: 50,
|
|
73
|
-
requestIdLength: 128
|
|
57
|
+
requestIdLength: 128,
|
|
58
|
+
checkInIdLength: 64
|
|
74
59
|
};
|
|
75
60
|
|
|
61
|
+
// src/context.ts
|
|
62
|
+
var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
|
|
63
|
+
function globalState() {
|
|
64
|
+
const holder = globalThis;
|
|
65
|
+
let state2 = holder[STATE_KEY];
|
|
66
|
+
if (!state2) {
|
|
67
|
+
state2 = { storage: new import_node_async_hooks.AsyncLocalStorage() };
|
|
68
|
+
holder[STATE_KEY] = state2;
|
|
69
|
+
}
|
|
70
|
+
return state2;
|
|
71
|
+
}
|
|
72
|
+
function currentContext() {
|
|
73
|
+
return globalState().storage.getStore();
|
|
74
|
+
}
|
|
75
|
+
function runWithContext(context, fn) {
|
|
76
|
+
return globalState().storage.run(context, fn);
|
|
77
|
+
}
|
|
78
|
+
var SAFE_REQUEST_ID = /^[\x21-\x7e]+$/;
|
|
79
|
+
function requestIdFrom(incoming) {
|
|
80
|
+
const value = incoming?.trim();
|
|
81
|
+
return value && value.length <= LIMITS.requestIdLength && SAFE_REQUEST_ID.test(value) ? value : (0, import_node_crypto.randomUUID)();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/express.ts
|
|
85
|
+
var import_node_perf_hooks = require("perf_hooks");
|
|
86
|
+
|
|
76
87
|
// src/redact.ts
|
|
77
|
-
var
|
|
88
|
+
var import_node_crypto2 = require("crypto");
|
|
78
89
|
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
79
90
|
"pass",
|
|
80
91
|
"passwd",
|
|
@@ -120,16 +131,16 @@ function fingerprintSlot() {
|
|
|
120
131
|
const holder = globalThis;
|
|
121
132
|
let slot = holder[KEY_SLOT];
|
|
122
133
|
if (!slot) {
|
|
123
|
-
slot = { key: (0,
|
|
134
|
+
slot = { key: (0, import_node_crypto2.randomBytes)(32) };
|
|
124
135
|
holder[KEY_SLOT] = slot;
|
|
125
136
|
}
|
|
126
137
|
return slot;
|
|
127
138
|
}
|
|
128
139
|
function setFingerprintSecret(secret) {
|
|
129
|
-
fingerprintSlot().key = secret ? (0,
|
|
140
|
+
fingerprintSlot().key = secret ? (0, import_node_crypto2.createHash)("sha256").update(`@oconde/radar:${secret}`).digest() : (0, import_node_crypto2.randomBytes)(32);
|
|
130
141
|
}
|
|
131
142
|
function fingerprint(value) {
|
|
132
|
-
return (0,
|
|
143
|
+
return (0, import_node_crypto2.createHmac)("sha256", fingerprintSlot().key).update(value).digest("hex").slice(0, 8);
|
|
133
144
|
}
|
|
134
145
|
function words(name) {
|
|
135
146
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
@@ -273,6 +284,40 @@ function requestRoute(req, route) {
|
|
|
273
284
|
if (route) return route;
|
|
274
285
|
return typeof req.route?.path === "string" ? req.route.path : void 0;
|
|
275
286
|
}
|
|
287
|
+
var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
|
|
288
|
+
function pathSegments(value) {
|
|
289
|
+
const segments = value.split("/");
|
|
290
|
+
while (segments.length > 1 && segments[segments.length - 1] === "") segments.pop();
|
|
291
|
+
return segments;
|
|
292
|
+
}
|
|
293
|
+
function decodeSegment(value) {
|
|
294
|
+
try {
|
|
295
|
+
return decodeURIComponent(value);
|
|
296
|
+
} catch {
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function routeParams(req, path) {
|
|
301
|
+
if (typeof req.route?.path !== "string") return {};
|
|
302
|
+
const pattern = pathSegments(`${typeof req.baseUrl === "string" ? req.baseUrl : ""}${req.route.path}`).filter((segment, index) => index > 0 || segment !== "");
|
|
303
|
+
const values = pathSegments(path);
|
|
304
|
+
if (pattern.length > values.length) return {};
|
|
305
|
+
const offset = values.length - pattern.length;
|
|
306
|
+
const result = {};
|
|
307
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
308
|
+
const value = values[offset + index];
|
|
309
|
+
const param = ROUTE_PARAM.exec(pattern[index]);
|
|
310
|
+
if (!param) {
|
|
311
|
+
if (pattern[index] !== value) return {};
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (value) result[param[1]] = decodeSegment(value);
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
function pathParams(req, path) {
|
|
319
|
+
return { ...routeParams(req, path), ...req.params ?? {} };
|
|
320
|
+
}
|
|
276
321
|
function redactPath(path, params, mode) {
|
|
277
322
|
if (mode === "none" || !params) return path;
|
|
278
323
|
let result = path;
|
|
@@ -288,7 +333,7 @@ function safeUrl(req, mode) {
|
|
|
288
333
|
const queryStart = url.indexOf("?");
|
|
289
334
|
const path = queryStart >= 0 ? url.slice(0, queryStart) : url;
|
|
290
335
|
const query = queryStart >= 0 ? url.slice(queryStart) : "";
|
|
291
|
-
return redactUrl(redactPath(path, req
|
|
336
|
+
return redactUrl(redactPath(path, pathParams(req, path), mode) + query, mode);
|
|
292
337
|
}
|
|
293
338
|
function redactUrl(url, mode) {
|
|
294
339
|
const queryStart = url.indexOf("?");
|
|
@@ -356,8 +401,7 @@ function createMiddleware(logger) {
|
|
|
356
401
|
return (req, res, next) => {
|
|
357
402
|
let context;
|
|
358
403
|
try {
|
|
359
|
-
const
|
|
360
|
-
const requestId = incoming && incoming.length <= LIMITS.requestIdLength ? incoming : (0, import_node_crypto2.randomUUID)();
|
|
404
|
+
const requestId = requestIdFrom(headerValue(req.headers["x-request-id"]));
|
|
361
405
|
context = { requestId, startedAt: import_node_perf_hooks.performance.now(), req };
|
|
362
406
|
res.setHeader("x-request-id", requestId);
|
|
363
407
|
if (logger.shouldLogRequest(req)) res.once("finish", () => logger.logHttpRequest(context, res.statusCode));
|
|
@@ -448,16 +492,92 @@ var import_promises = require("fs/promises");
|
|
|
448
492
|
var import_node_module = require("module");
|
|
449
493
|
var import_node_path = require("path");
|
|
450
494
|
var import_node_url = require("url");
|
|
495
|
+
|
|
496
|
+
// src/function-name.ts
|
|
497
|
+
var IDENTIFIER = "[A-Za-z_$][\\w$]*";
|
|
498
|
+
var MAX_LINES_UP = 400;
|
|
499
|
+
var MAX_HEADER_LINES = 6;
|
|
500
|
+
var MAX_NAME_LENGTH = 100;
|
|
501
|
+
var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
|
|
502
|
+
var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
|
|
503
|
+
var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
|
|
504
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "await", "else", "do", "try"]);
|
|
505
|
+
var NAME_PATTERNS = [
|
|
506
|
+
new RegExp(`\\bfunction\\s*\\*?\\s*(${IDENTIFIER})\\s*\\(`),
|
|
507
|
+
new RegExp(`(${IDENTIFIER})\\s*[:=]\\s*(?:async\\s+)?function\\b`),
|
|
508
|
+
new RegExp(`(${IDENTIFIER})\\s*(?::\\s*[^=]+)?=\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`),
|
|
509
|
+
new RegExp(`(?:^|[{,(]\\s*)(${IDENTIFIER})\\s*:\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`)
|
|
510
|
+
];
|
|
511
|
+
var METHOD_PATTERN = new RegExp(`^\\s*(?:(?:export|default|public|private|protected|static|async|override|readonly|get|set)\\s+)*\\*?\\s*(${IDENTIFIER})\\s*(?:<[^>]*>)?\\s*\\([^]*\\)\\s*(?::\\s*[^{]+)?$`);
|
|
512
|
+
var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
|
|
513
|
+
function codeOnly(text) {
|
|
514
|
+
return text.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, '""').replace(/\/\*.*?\*\//g, "").replace(/(^|[^:])\/\/.*$/, "$1");
|
|
515
|
+
}
|
|
516
|
+
function parenBalance(text) {
|
|
517
|
+
let balance = 0;
|
|
518
|
+
for (const char of text) {
|
|
519
|
+
if (char === "(") balance += 1;
|
|
520
|
+
else if (char === ")") balance -= 1;
|
|
521
|
+
}
|
|
522
|
+
return balance;
|
|
523
|
+
}
|
|
524
|
+
function headerOf(lines, index, prefix) {
|
|
525
|
+
let header = prefix;
|
|
526
|
+
for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
|
|
527
|
+
if (header.trim() !== "" && parenBalance(header) >= 0) break;
|
|
528
|
+
header = `${codeOnly(lines[previous] ?? "")} ${header}`;
|
|
529
|
+
}
|
|
530
|
+
return header;
|
|
531
|
+
}
|
|
532
|
+
function nameIn(header) {
|
|
533
|
+
const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
|
|
534
|
+
for (const pattern of patterns) {
|
|
535
|
+
const name = pattern.exec(header)?.[1];
|
|
536
|
+
if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
|
|
537
|
+
}
|
|
538
|
+
return void 0;
|
|
539
|
+
}
|
|
540
|
+
function enclosingFunctionName(lines, line, column) {
|
|
541
|
+
const start = line - 1;
|
|
542
|
+
if (start < 0 || start >= lines.length) return void 0;
|
|
543
|
+
let depth = 0;
|
|
544
|
+
for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
|
|
545
|
+
const raw = lines[index] ?? "";
|
|
546
|
+
if (index < start && MARKUP_BOUNDARY.test(raw)) return void 0;
|
|
547
|
+
const code = codeOnly(index === start && column ? raw.slice(0, Math.max(0, column - 1)) : raw);
|
|
548
|
+
for (let position = code.length - 1; position >= 0; position--) {
|
|
549
|
+
const char = code[position];
|
|
550
|
+
if (char === "}") {
|
|
551
|
+
depth += 1;
|
|
552
|
+
} else if (char === "{") {
|
|
553
|
+
if (depth > 0) {
|
|
554
|
+
depth -= 1;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const header = headerOf(lines, index, code.slice(0, position));
|
|
558
|
+
if (CLASS_BODY.test(header)) return void 0;
|
|
559
|
+
if (CONTROL_BLOCK.test(header)) continue;
|
|
560
|
+
const name = nameIn(header);
|
|
561
|
+
if (name) return name;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/source.ts
|
|
451
569
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
452
570
|
var MAX_CACHED_FILES = 50;
|
|
453
571
|
var MAPPING_URL = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
|
|
454
572
|
var cache = /* @__PURE__ */ new Map();
|
|
455
|
-
async function enrichFrames(frames, cwd = process.cwd()) {
|
|
573
|
+
async function enrichFrames(frames, cwd = process.cwd(), options = {}) {
|
|
456
574
|
const result = [];
|
|
457
575
|
let withContext = 0;
|
|
458
576
|
for (const frame of frames) {
|
|
459
577
|
try {
|
|
460
|
-
const { frame:
|
|
578
|
+
const { frame: located, lines, original } = await mapFrame(frame, options.mapLocator);
|
|
579
|
+
const named = options.inferFunctionNames && original && !located.fn && lines && located.line ? withInferredName(located, lines) : located;
|
|
580
|
+
const mapped = named.inApp && isLibraryPath(named.file) ? { ...named, inApp: false } : named;
|
|
461
581
|
let next = mapped;
|
|
462
582
|
if (mapped.inApp && lines && mapped.line && withContext < LIMITS.contextFrames) {
|
|
463
583
|
const context = contextFor(lines, mapped.line);
|
|
@@ -473,29 +593,36 @@ async function enrichFrames(frames, cwd = process.cwd()) {
|
|
|
473
593
|
}
|
|
474
594
|
return result;
|
|
475
595
|
}
|
|
476
|
-
async function enrichException(exception, cwd = process.cwd()) {
|
|
477
|
-
const frames = await enrichFrames(exception.frames, cwd);
|
|
596
|
+
async function enrichException(exception, cwd = process.cwd(), options = {}) {
|
|
597
|
+
const frames = await enrichFrames(exception.frames, cwd, options);
|
|
478
598
|
if (!exception.cause) return { ...exception, frames };
|
|
479
|
-
return { ...exception, frames, cause: await enrichException(exception.cause, cwd) };
|
|
599
|
+
return { ...exception, frames, cause: await enrichException(exception.cause, cwd, options) };
|
|
600
|
+
}
|
|
601
|
+
function withInferredName(frame, lines) {
|
|
602
|
+
const fn = enclosingFunctionName(lines, frame.line ?? 0, frame.col);
|
|
603
|
+
return fn ? { ...frame, fn } : frame;
|
|
604
|
+
}
|
|
605
|
+
function isLibraryPath(file) {
|
|
606
|
+
return file.includes("/node_modules/") || file.includes("\\node_modules\\");
|
|
480
607
|
}
|
|
481
608
|
function relativize(file, cwd) {
|
|
482
609
|
if (!(0, import_node_path.isAbsolute)(file)) return file;
|
|
483
610
|
const path = (0, import_node_path.relative)(cwd, file);
|
|
484
611
|
return path.startsWith("..") || (0, import_node_path.isAbsolute)(path) ? file : path.split(import_node_path.sep).join("/");
|
|
485
612
|
}
|
|
486
|
-
async function mapFrame(frame) {
|
|
487
|
-
if (!frame.line || !(0, import_node_path.isAbsolute)(frame.file)) return { frame, lines: null };
|
|
488
|
-
const file = await loadFile(frame.file);
|
|
489
|
-
if (!file) return { frame, lines: null };
|
|
490
|
-
if (!file.map) return { frame, lines: file.lines };
|
|
613
|
+
async function mapFrame(frame, locator) {
|
|
614
|
+
if (!frame.line || !(0, import_node_path.isAbsolute)(frame.file)) return { frame, lines: null, original: false };
|
|
615
|
+
const file = await loadFile(frame.file, locator);
|
|
616
|
+
if (!file) return { frame, lines: null, original: false };
|
|
617
|
+
if (!file.map) return { frame, lines: file.lines, original: false };
|
|
491
618
|
const entry = file.map.map.findEntry(frame.line - 1, (frame.col ?? 1) - 1);
|
|
492
|
-
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines };
|
|
619
|
+
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines, original: false };
|
|
493
620
|
const originalPath = resolveSource(file.map, entry.originalSource);
|
|
494
621
|
const mapped = { ...frame, file: originalPath, line: entry.originalLine + 1, col: (entry.originalColumn ?? 0) + 1 };
|
|
495
622
|
const lines = embeddedLines(file.map, file.map.sources.indexOf(entry.originalSource));
|
|
496
|
-
if (lines) return { frame: mapped, lines };
|
|
623
|
+
if (lines) return { frame: mapped, lines, original: true };
|
|
497
624
|
const original = await loadFile(originalPath);
|
|
498
|
-
return { frame: mapped, lines: original?.lines ?? null };
|
|
625
|
+
return { frame: mapped, lines: original?.lines ?? null, original: true };
|
|
499
626
|
}
|
|
500
627
|
function embeddedLines(map, index) {
|
|
501
628
|
if (index < 0) return null;
|
|
@@ -527,26 +654,27 @@ function resolveSource(map, source) {
|
|
|
527
654
|
}
|
|
528
655
|
return (0, import_node_path.isAbsolute)(source) ? source : (0, import_node_path.resolve)(map.baseDir, source);
|
|
529
656
|
}
|
|
530
|
-
function loadFile(path) {
|
|
531
|
-
const
|
|
657
|
+
function loadFile(path, locator) {
|
|
658
|
+
const key = locator ? `${path}\0located` : path;
|
|
659
|
+
const cached = cache.get(key);
|
|
532
660
|
if (cached) {
|
|
533
|
-
cache.delete(
|
|
534
|
-
cache.set(
|
|
661
|
+
cache.delete(key);
|
|
662
|
+
cache.set(key, cached);
|
|
535
663
|
return cached;
|
|
536
664
|
}
|
|
537
|
-
const pending = readSource(path).catch(() => null);
|
|
538
|
-
cache.set(
|
|
665
|
+
const pending = readSource(path, locator).catch(() => null);
|
|
666
|
+
cache.set(key, pending);
|
|
539
667
|
if (cache.size > MAX_CACHED_FILES) {
|
|
540
668
|
const oldest = cache.keys().next().value;
|
|
541
669
|
if (oldest !== void 0) cache.delete(oldest);
|
|
542
670
|
}
|
|
543
671
|
return pending;
|
|
544
672
|
}
|
|
545
|
-
async function readSource(path) {
|
|
673
|
+
async function readSource(path, locator) {
|
|
546
674
|
const text = await readText(path);
|
|
547
675
|
if (text === null) return null;
|
|
548
676
|
const lines = text.split(/\r?\n/);
|
|
549
|
-
return { lines, map: await loadMap(path, lines) };
|
|
677
|
+
return { lines, map: await loadMap(path, lines, locator) };
|
|
550
678
|
}
|
|
551
679
|
async function readText(path) {
|
|
552
680
|
try {
|
|
@@ -557,7 +685,7 @@ async function readText(path) {
|
|
|
557
685
|
return null;
|
|
558
686
|
}
|
|
559
687
|
}
|
|
560
|
-
async function loadMap(path, lines) {
|
|
688
|
+
async function loadMap(path, lines, locator) {
|
|
561
689
|
let url;
|
|
562
690
|
for (let index = lines.length - 1; index >= 0 && index >= lines.length - 5; index--) {
|
|
563
691
|
const match = MAPPING_URL.exec(lines[index] ?? "");
|
|
@@ -566,10 +694,15 @@ async function loadMap(path, lines) {
|
|
|
566
694
|
break;
|
|
567
695
|
}
|
|
568
696
|
}
|
|
569
|
-
if (!url) return null;
|
|
570
697
|
let raw;
|
|
571
698
|
let mapDir;
|
|
572
|
-
|
|
699
|
+
const located = url ? null : locator ? locator(path) : `${path}.map`;
|
|
700
|
+
if (located) {
|
|
701
|
+
raw = await readText(located);
|
|
702
|
+
mapDir = (0, import_node_path.dirname)(located);
|
|
703
|
+
} else if (!url) {
|
|
704
|
+
return null;
|
|
705
|
+
} else if (url.startsWith("data:")) {
|
|
573
706
|
const comma = url.indexOf(",");
|
|
574
707
|
const encoded = url.slice(comma + 1);
|
|
575
708
|
raw = url.slice(0, comma).endsWith(";base64") ? Buffer.from(encoded, "base64").toString("utf8") : decodeURIComponent(encoded);
|
|
@@ -598,12 +731,13 @@ async function loadMap(path, lines) {
|
|
|
598
731
|
var import_node_url2 = require("url");
|
|
599
732
|
var WITH_FUNCTION = /^at (?:async )?(.+?) \((.+)\)$/;
|
|
600
733
|
var LOCATION = /^(.+):(\d+):(\d+)$/;
|
|
734
|
+
var MAX_FRAME_LINE = 1e3;
|
|
601
735
|
function parseStack(stack) {
|
|
602
736
|
if (!stack) return [];
|
|
603
737
|
const frames = [];
|
|
604
738
|
for (const raw of stack.split("\n")) {
|
|
605
739
|
const line = raw.trim();
|
|
606
|
-
if (!line.startsWith("at ")) continue;
|
|
740
|
+
if (line.length > MAX_FRAME_LINE || !line.startsWith("at ")) continue;
|
|
607
741
|
const withFunction = WITH_FUNCTION.exec(line);
|
|
608
742
|
const fn = withFunction?.[1];
|
|
609
743
|
const location = withFunction ? withFunction[2] ?? "" : line.replace(/^at (?:async )?/, "");
|
|
@@ -698,6 +832,7 @@ var Transport = class {
|
|
|
698
832
|
inFlight = null;
|
|
699
833
|
nextAttemptAt = 0;
|
|
700
834
|
backoffMs = 0;
|
|
835
|
+
unauthorizedUntil = 0;
|
|
701
836
|
stopped = false;
|
|
702
837
|
warned = /* @__PURE__ */ new Set();
|
|
703
838
|
timer;
|
|
@@ -787,10 +922,8 @@ var Transport = class {
|
|
|
787
922
|
this.nextAttemptAt = Date.now() + Math.max(this.backoffMs, outcome.retryAfterMs ?? 0);
|
|
788
923
|
return false;
|
|
789
924
|
case "unauthorized":
|
|
790
|
-
this.droppedCount += events.length
|
|
791
|
-
this.
|
|
792
|
-
this.nextAttemptAt = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
793
|
-
this.warn("unauthorized", `chave do Radar recusada (${outcome.status}); envios pausados por 10 minutos`, true);
|
|
925
|
+
this.droppedCount += events.length;
|
|
926
|
+
this.pauseUnauthorized(outcome.status);
|
|
794
927
|
return false;
|
|
795
928
|
case "rejected":
|
|
796
929
|
this.droppedCount += events.length;
|
|
@@ -798,6 +931,47 @@ var Transport = class {
|
|
|
798
931
|
return true;
|
|
799
932
|
}
|
|
800
933
|
}
|
|
934
|
+
async checkIn(slug, payload) {
|
|
935
|
+
if (this.stopped || !this.config.checkInBaseUrl || Date.now() < this.unauthorizedUntil) return;
|
|
936
|
+
let response;
|
|
937
|
+
try {
|
|
938
|
+
response = await fetch(this.config.checkInBaseUrl + encodeURIComponent(slug), {
|
|
939
|
+
method: "POST",
|
|
940
|
+
headers: this.headers(),
|
|
941
|
+
body: JSON.stringify(payload),
|
|
942
|
+
redirect: "error",
|
|
943
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
|
|
944
|
+
});
|
|
945
|
+
} catch (error) {
|
|
946
|
+
this.warn("checkin_network", `check-in no Radar falhou: ${describeError(error)}`, false);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
await response.arrayBuffer().catch(() => void 0);
|
|
950
|
+
if (response.ok) return;
|
|
951
|
+
if (response.status === 401 || response.status === 403) {
|
|
952
|
+
this.pauseUnauthorized(response.status);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (response.status === 404) {
|
|
956
|
+
this.warn(`checkin_unknown_${slug}`, `tarefa agendada "${slug}" n\xE3o existe no projeto da chave; crie o monitor com esse slug no Radar`, true);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
this.warn(`checkin_rejected_${response.status}`, `check-in recusado pelo Radar (${response.status})`, false);
|
|
960
|
+
}
|
|
961
|
+
pauseUnauthorized(status) {
|
|
962
|
+
this.droppedCount += this.queue.length;
|
|
963
|
+
this.queue = [];
|
|
964
|
+
this.unauthorizedUntil = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
965
|
+
this.nextAttemptAt = Math.max(this.nextAttemptAt, this.unauthorizedUntil);
|
|
966
|
+
this.warn("unauthorized", `chave do Radar recusada (${status}); envios pausados por 10 minutos`, true);
|
|
967
|
+
}
|
|
968
|
+
headers() {
|
|
969
|
+
return {
|
|
970
|
+
"content-type": "application/json",
|
|
971
|
+
authorization: `Bearer ${this.config.key}`,
|
|
972
|
+
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
973
|
+
};
|
|
974
|
+
}
|
|
801
975
|
requeue(events) {
|
|
802
976
|
this.queue.unshift(...events);
|
|
803
977
|
const overflow = this.queue.length - this.maxQueue;
|
|
@@ -809,11 +983,7 @@ var Transport = class {
|
|
|
809
983
|
async post(batch) {
|
|
810
984
|
const json = JSON.stringify(batch);
|
|
811
985
|
const compress = Buffer.byteLength(json) > GZIP_THRESHOLD_BYTES;
|
|
812
|
-
const headers =
|
|
813
|
-
"content-type": "application/json",
|
|
814
|
-
authorization: `Bearer ${this.config.key}`,
|
|
815
|
-
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
816
|
-
};
|
|
986
|
+
const headers = this.headers();
|
|
817
987
|
if (compress) headers["content-encoding"] = "gzip";
|
|
818
988
|
let response;
|
|
819
989
|
try {
|
|
@@ -867,9 +1037,12 @@ function withTimeout(promise, ms) {
|
|
|
867
1037
|
|
|
868
1038
|
// src/version.ts
|
|
869
1039
|
var SDK_NAME = "@oconde/radar";
|
|
870
|
-
var SDK_VERSION = true ? "0.
|
|
1040
|
+
var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
871
1041
|
|
|
872
1042
|
// src/client.ts
|
|
1043
|
+
var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
|
|
1044
|
+
var MAX_CHECK_IN_ENVIRONMENT = 64;
|
|
1045
|
+
var MAX_CHECK_IN_RELEASE = 128;
|
|
873
1046
|
var RadarClient = class {
|
|
874
1047
|
options = resolveOptions({}, {});
|
|
875
1048
|
transport = null;
|
|
@@ -898,6 +1071,7 @@ var RadarClient = class {
|
|
|
898
1071
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
899
1072
|
environment: this.options.environment,
|
|
900
1073
|
...this.options.release ? { release: this.options.release } : {},
|
|
1074
|
+
checkInBaseUrl: this.options.endpoint + CHECKIN_PATH,
|
|
901
1075
|
onWarning: (message, always) => always ? this.writeWarning(message) : this.debugWarn(message)
|
|
902
1076
|
});
|
|
903
1077
|
}
|
|
@@ -959,6 +1133,51 @@ var RadarClient = class {
|
|
|
959
1133
|
if (currentContext()) return execute();
|
|
960
1134
|
return runWithContext({ requestId: (0, import_node_crypto3.randomUUID)(), startedAt: import_node_perf_hooks3.performance.now() }, execute);
|
|
961
1135
|
}
|
|
1136
|
+
withContext(fn, options = {}) {
|
|
1137
|
+
return runWithContext({ requestId: requestIdFrom(options.requestId), startedAt: import_node_perf_hooks3.performance.now() }, fn);
|
|
1138
|
+
}
|
|
1139
|
+
async checkIn(slug, status, options = {}) {
|
|
1140
|
+
try {
|
|
1141
|
+
if (!this.transport || this.closed) return;
|
|
1142
|
+
if (typeof slug !== "string" || !CHECKIN_SLUG.test(slug)) {
|
|
1143
|
+
this.debugWarn(`slug de tarefa agendada inv\xE1lido: ${String(slug).slice(0, 80)}`);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (!CHECKIN_STATUSES.includes(status)) return;
|
|
1147
|
+
const checkInId = typeof options.checkInId === "string" && options.checkInId ? options.checkInId.slice(0, LIMITS.checkInIdLength) : void 0;
|
|
1148
|
+
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;
|
|
1149
|
+
const environment = this.options.environment.slice(0, MAX_CHECK_IN_ENVIRONMENT) || "production";
|
|
1150
|
+
const release = this.options.release?.slice(0, MAX_CHECK_IN_RELEASE);
|
|
1151
|
+
const payload = {
|
|
1152
|
+
status,
|
|
1153
|
+
...checkInId ? { checkInId } : {},
|
|
1154
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1155
|
+
environment,
|
|
1156
|
+
...release ? { release } : {}
|
|
1157
|
+
};
|
|
1158
|
+
await this.transport.checkIn(slug, payload);
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
this.debugWarn(`check-in falhou: ${describeError(error)}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
async cron(slug, fn) {
|
|
1164
|
+
const checkInId = (0, import_node_crypto3.randomUUID)();
|
|
1165
|
+
const started = import_node_perf_hooks3.performance.now();
|
|
1166
|
+
const execute = async () => {
|
|
1167
|
+
await this.checkIn(slug, "in_progress", { checkInId });
|
|
1168
|
+
try {
|
|
1169
|
+
const result = await fn();
|
|
1170
|
+
await this.checkIn(slug, "ok", { checkInId, durationMs: elapsedSince(started) });
|
|
1171
|
+
return result;
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
this.captureError(error, { cron: slug });
|
|
1174
|
+
await this.checkIn(slug, "error", { checkInId, durationMs: elapsedSince(started) });
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
if (currentContext()) return execute();
|
|
1179
|
+
return this.withContext(execute);
|
|
1180
|
+
}
|
|
962
1181
|
middleware() {
|
|
963
1182
|
return createMiddleware(this);
|
|
964
1183
|
}
|
|
@@ -976,7 +1195,7 @@ var RadarClient = class {
|
|
|
976
1195
|
() => this.emitLog(level, "http.request", {
|
|
977
1196
|
method: (req.method ?? "GET").toUpperCase(),
|
|
978
1197
|
...route ? { route } : {},
|
|
979
|
-
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), req
|
|
1198
|
+
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
|
|
980
1199
|
status,
|
|
981
1200
|
durationMs: elapsedSince(context.startedAt)
|
|
982
1201
|
})
|
|
@@ -1013,31 +1232,40 @@ var RadarClient = class {
|
|
|
1013
1232
|
this.print(event);
|
|
1014
1233
|
this.transport?.enqueue(event);
|
|
1015
1234
|
}
|
|
1235
|
+
captureException(exception, options = {}) {
|
|
1236
|
+
this.safely(() => this.emitException(exception, options));
|
|
1237
|
+
}
|
|
1016
1238
|
emitError(error, data, level, handled) {
|
|
1017
1239
|
if (this.closed) return;
|
|
1018
1240
|
if (typeof error === "object" && error !== null) {
|
|
1019
1241
|
if (this.captured.has(error)) return;
|
|
1020
1242
|
this.captured.add(error);
|
|
1021
1243
|
}
|
|
1244
|
+
this.emitException(exceptionInfo(error), { level, handled, ...data !== void 0 ? { attrs: data } : {} }, error instanceof Error ? error.stack : void 0);
|
|
1245
|
+
}
|
|
1246
|
+
emitException(exception, options, stack) {
|
|
1247
|
+
if (this.closed) return;
|
|
1022
1248
|
const context = currentContext();
|
|
1023
1249
|
const mode = this.options.redact;
|
|
1250
|
+
const request = options.request === null ? void 0 : options.request ?? (context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0);
|
|
1251
|
+
const runtime = options.runtime === void 0 ? runtimeInfo() : options.runtime;
|
|
1024
1252
|
const event = {
|
|
1025
1253
|
type: "error",
|
|
1026
1254
|
ts: Date.now(),
|
|
1027
|
-
level,
|
|
1028
|
-
handled,
|
|
1029
|
-
exception
|
|
1255
|
+
level: options.level ?? "error",
|
|
1256
|
+
handled: options.handled ?? true,
|
|
1257
|
+
exception,
|
|
1030
1258
|
...context ? { requestId: context.requestId } : {},
|
|
1031
|
-
...
|
|
1259
|
+
...request ? { request } : {},
|
|
1032
1260
|
...context?.user ? { user: context.user } : {},
|
|
1033
|
-
runtime:
|
|
1034
|
-
...
|
|
1261
|
+
...runtime ? { runtime } : {},
|
|
1262
|
+
...options.attrs !== void 0 ? { attrs: prepareAttrs(options.attrs, mode) } : {}
|
|
1035
1263
|
};
|
|
1036
|
-
this.print(event,
|
|
1264
|
+
this.print(event, stack);
|
|
1037
1265
|
if (!this.transport) return;
|
|
1038
1266
|
this.transport.enqueue(
|
|
1039
|
-
() => enrichException(event.exception).then(
|
|
1040
|
-
(
|
|
1267
|
+
() => enrichException(event.exception, process.cwd(), options.enrich).then(
|
|
1268
|
+
(enriched) => ({ ...event, exception: enriched }),
|
|
1041
1269
|
() => event
|
|
1042
1270
|
)
|
|
1043
1271
|
);
|