@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/nest/index.cjs
CHANGED
|
@@ -50,6 +50,33 @@ var import_rxjs = require("rxjs");
|
|
|
50
50
|
|
|
51
51
|
// src/context.ts
|
|
52
52
|
var import_node_async_hooks = require("async_hooks");
|
|
53
|
+
var import_node_crypto = require("crypto");
|
|
54
|
+
|
|
55
|
+
// src/protocol/index.ts
|
|
56
|
+
var INGEST_PATH = "/api/v1/events";
|
|
57
|
+
var CHECKIN_PATH = "/api/v1/checkins/";
|
|
58
|
+
var CHECKIN_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
59
|
+
var CHECKIN_STATUSES = ["in_progress", "ok", "error"];
|
|
60
|
+
var LIMITS = {
|
|
61
|
+
batchBytes: 1e6,
|
|
62
|
+
batchEvents: 500,
|
|
63
|
+
messageLength: 2e3,
|
|
64
|
+
stringLength: 2e3,
|
|
65
|
+
attrsBytes: 16384,
|
|
66
|
+
bodyBytes: 16384,
|
|
67
|
+
headers: 50,
|
|
68
|
+
frames: 50,
|
|
69
|
+
contextFrames: 10,
|
|
70
|
+
contextLines: 5,
|
|
71
|
+
contextLineLength: 300,
|
|
72
|
+
causeDepth: 5,
|
|
73
|
+
depth: 6,
|
|
74
|
+
arrayItems: 50,
|
|
75
|
+
requestIdLength: 128,
|
|
76
|
+
checkInIdLength: 64
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/context.ts
|
|
53
80
|
var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
|
|
54
81
|
function globalState() {
|
|
55
82
|
const holder = globalThis;
|
|
@@ -66,6 +93,11 @@ function currentContext() {
|
|
|
66
93
|
function runWithContext(context, fn) {
|
|
67
94
|
return globalState().storage.run(context, fn);
|
|
68
95
|
}
|
|
96
|
+
var SAFE_REQUEST_ID = /^[\x21-\x7e]+$/;
|
|
97
|
+
function requestIdFrom(incoming) {
|
|
98
|
+
const value = incoming?.trim();
|
|
99
|
+
return value && value.length <= LIMITS.requestIdLength && SAFE_REQUEST_ID.test(value) ? value : (0, import_node_crypto.randomUUID)();
|
|
100
|
+
}
|
|
69
101
|
|
|
70
102
|
// src/client.ts
|
|
71
103
|
var import_node_crypto3 = require("crypto");
|
|
@@ -73,31 +105,10 @@ var import_node_os = require("os");
|
|
|
73
105
|
var import_node_perf_hooks3 = require("perf_hooks");
|
|
74
106
|
|
|
75
107
|
// src/express.ts
|
|
76
|
-
var import_node_crypto2 = require("crypto");
|
|
77
108
|
var import_node_perf_hooks = require("perf_hooks");
|
|
78
109
|
|
|
79
|
-
// src/protocol/index.ts
|
|
80
|
-
var INGEST_PATH = "/api/v1/events";
|
|
81
|
-
var LIMITS = {
|
|
82
|
-
batchBytes: 1e6,
|
|
83
|
-
batchEvents: 500,
|
|
84
|
-
messageLength: 2e3,
|
|
85
|
-
stringLength: 2e3,
|
|
86
|
-
attrsBytes: 16384,
|
|
87
|
-
bodyBytes: 16384,
|
|
88
|
-
headers: 50,
|
|
89
|
-
frames: 50,
|
|
90
|
-
contextFrames: 10,
|
|
91
|
-
contextLines: 5,
|
|
92
|
-
contextLineLength: 300,
|
|
93
|
-
causeDepth: 5,
|
|
94
|
-
depth: 6,
|
|
95
|
-
arrayItems: 50,
|
|
96
|
-
requestIdLength: 128
|
|
97
|
-
};
|
|
98
|
-
|
|
99
110
|
// src/redact.ts
|
|
100
|
-
var
|
|
111
|
+
var import_node_crypto2 = require("crypto");
|
|
101
112
|
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
102
113
|
"pass",
|
|
103
114
|
"passwd",
|
|
@@ -143,16 +154,16 @@ function fingerprintSlot() {
|
|
|
143
154
|
const holder = globalThis;
|
|
144
155
|
let slot = holder[KEY_SLOT];
|
|
145
156
|
if (!slot) {
|
|
146
|
-
slot = { key: (0,
|
|
157
|
+
slot = { key: (0, import_node_crypto2.randomBytes)(32) };
|
|
147
158
|
holder[KEY_SLOT] = slot;
|
|
148
159
|
}
|
|
149
160
|
return slot;
|
|
150
161
|
}
|
|
151
162
|
function setFingerprintSecret(secret) {
|
|
152
|
-
fingerprintSlot().key = secret ? (0,
|
|
163
|
+
fingerprintSlot().key = secret ? (0, import_node_crypto2.createHash)("sha256").update(`@oconde/radar:${secret}`).digest() : (0, import_node_crypto2.randomBytes)(32);
|
|
153
164
|
}
|
|
154
165
|
function fingerprint(value) {
|
|
155
|
-
return (0,
|
|
166
|
+
return (0, import_node_crypto2.createHmac)("sha256", fingerprintSlot().key).update(value).digest("hex").slice(0, 8);
|
|
156
167
|
}
|
|
157
168
|
function words(name) {
|
|
158
169
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
@@ -296,6 +307,40 @@ function requestRoute(req, route) {
|
|
|
296
307
|
if (route) return route;
|
|
297
308
|
return typeof req.route?.path === "string" ? req.route.path : void 0;
|
|
298
309
|
}
|
|
310
|
+
var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
|
|
311
|
+
function pathSegments(value) {
|
|
312
|
+
const segments = value.split("/");
|
|
313
|
+
while (segments.length > 1 && segments[segments.length - 1] === "") segments.pop();
|
|
314
|
+
return segments;
|
|
315
|
+
}
|
|
316
|
+
function decodeSegment(value) {
|
|
317
|
+
try {
|
|
318
|
+
return decodeURIComponent(value);
|
|
319
|
+
} catch {
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function routeParams(req, path) {
|
|
324
|
+
if (typeof req.route?.path !== "string") return {};
|
|
325
|
+
const pattern = pathSegments(`${typeof req.baseUrl === "string" ? req.baseUrl : ""}${req.route.path}`).filter((segment, index) => index > 0 || segment !== "");
|
|
326
|
+
const values = pathSegments(path);
|
|
327
|
+
if (pattern.length > values.length) return {};
|
|
328
|
+
const offset = values.length - pattern.length;
|
|
329
|
+
const result = {};
|
|
330
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
331
|
+
const value = values[offset + index];
|
|
332
|
+
const param = ROUTE_PARAM.exec(pattern[index]);
|
|
333
|
+
if (!param) {
|
|
334
|
+
if (pattern[index] !== value) return {};
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (value) result[param[1]] = decodeSegment(value);
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
function pathParams(req, path) {
|
|
342
|
+
return { ...routeParams(req, path), ...req.params ?? {} };
|
|
343
|
+
}
|
|
299
344
|
function redactPath(path, params, mode) {
|
|
300
345
|
if (mode === "none" || !params) return path;
|
|
301
346
|
let result = path;
|
|
@@ -311,7 +356,7 @@ function safeUrl(req, mode) {
|
|
|
311
356
|
const queryStart = url.indexOf("?");
|
|
312
357
|
const path = queryStart >= 0 ? url.slice(0, queryStart) : url;
|
|
313
358
|
const query = queryStart >= 0 ? url.slice(queryStart) : "";
|
|
314
|
-
return redactUrl(redactPath(path, req
|
|
359
|
+
return redactUrl(redactPath(path, pathParams(req, path), mode) + query, mode);
|
|
315
360
|
}
|
|
316
361
|
function redactUrl(url, mode) {
|
|
317
362
|
const queryStart = url.indexOf("?");
|
|
@@ -379,8 +424,7 @@ function createMiddleware(logger) {
|
|
|
379
424
|
return (req, res, next) => {
|
|
380
425
|
let context;
|
|
381
426
|
try {
|
|
382
|
-
const
|
|
383
|
-
const requestId = incoming && incoming.length <= LIMITS.requestIdLength ? incoming : (0, import_node_crypto2.randomUUID)();
|
|
427
|
+
const requestId = requestIdFrom(headerValue(req.headers["x-request-id"]));
|
|
384
428
|
context = { requestId, startedAt: import_node_perf_hooks.performance.now(), req };
|
|
385
429
|
res.setHeader("x-request-id", requestId);
|
|
386
430
|
if (logger.shouldLogRequest(req)) res.once("finish", () => logger.logHttpRequest(context, res.statusCode));
|
|
@@ -471,16 +515,92 @@ var import_promises = require("fs/promises");
|
|
|
471
515
|
var import_node_module = require("module");
|
|
472
516
|
var import_node_path = require("path");
|
|
473
517
|
var import_node_url = require("url");
|
|
518
|
+
|
|
519
|
+
// src/function-name.ts
|
|
520
|
+
var IDENTIFIER = "[A-Za-z_$][\\w$]*";
|
|
521
|
+
var MAX_LINES_UP = 400;
|
|
522
|
+
var MAX_HEADER_LINES = 6;
|
|
523
|
+
var MAX_NAME_LENGTH = 100;
|
|
524
|
+
var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
|
|
525
|
+
var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
|
|
526
|
+
var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
|
|
527
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "await", "else", "do", "try"]);
|
|
528
|
+
var NAME_PATTERNS = [
|
|
529
|
+
new RegExp(`\\bfunction\\s*\\*?\\s*(${IDENTIFIER})\\s*\\(`),
|
|
530
|
+
new RegExp(`(${IDENTIFIER})\\s*[:=]\\s*(?:async\\s+)?function\\b`),
|
|
531
|
+
new RegExp(`(${IDENTIFIER})\\s*(?::\\s*[^=]+)?=\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`),
|
|
532
|
+
new RegExp(`(?:^|[{,(]\\s*)(${IDENTIFIER})\\s*:\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`)
|
|
533
|
+
];
|
|
534
|
+
var METHOD_PATTERN = new RegExp(`^\\s*(?:(?:export|default|public|private|protected|static|async|override|readonly|get|set)\\s+)*\\*?\\s*(${IDENTIFIER})\\s*(?:<[^>]*>)?\\s*\\([^]*\\)\\s*(?::\\s*[^{]+)?$`);
|
|
535
|
+
var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
|
|
536
|
+
function codeOnly(text) {
|
|
537
|
+
return text.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, '""').replace(/\/\*.*?\*\//g, "").replace(/(^|[^:])\/\/.*$/, "$1");
|
|
538
|
+
}
|
|
539
|
+
function parenBalance(text) {
|
|
540
|
+
let balance = 0;
|
|
541
|
+
for (const char of text) {
|
|
542
|
+
if (char === "(") balance += 1;
|
|
543
|
+
else if (char === ")") balance -= 1;
|
|
544
|
+
}
|
|
545
|
+
return balance;
|
|
546
|
+
}
|
|
547
|
+
function headerOf(lines, index, prefix) {
|
|
548
|
+
let header = prefix;
|
|
549
|
+
for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
|
|
550
|
+
if (header.trim() !== "" && parenBalance(header) >= 0) break;
|
|
551
|
+
header = `${codeOnly(lines[previous] ?? "")} ${header}`;
|
|
552
|
+
}
|
|
553
|
+
return header;
|
|
554
|
+
}
|
|
555
|
+
function nameIn(header) {
|
|
556
|
+
const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
|
|
557
|
+
for (const pattern of patterns) {
|
|
558
|
+
const name = pattern.exec(header)?.[1];
|
|
559
|
+
if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
|
|
560
|
+
}
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
function enclosingFunctionName(lines, line, column) {
|
|
564
|
+
const start = line - 1;
|
|
565
|
+
if (start < 0 || start >= lines.length) return void 0;
|
|
566
|
+
let depth = 0;
|
|
567
|
+
for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
|
|
568
|
+
const raw = lines[index] ?? "";
|
|
569
|
+
if (index < start && MARKUP_BOUNDARY.test(raw)) return void 0;
|
|
570
|
+
const code = codeOnly(index === start && column ? raw.slice(0, Math.max(0, column - 1)) : raw);
|
|
571
|
+
for (let position = code.length - 1; position >= 0; position--) {
|
|
572
|
+
const char = code[position];
|
|
573
|
+
if (char === "}") {
|
|
574
|
+
depth += 1;
|
|
575
|
+
} else if (char === "{") {
|
|
576
|
+
if (depth > 0) {
|
|
577
|
+
depth -= 1;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const header = headerOf(lines, index, code.slice(0, position));
|
|
581
|
+
if (CLASS_BODY.test(header)) return void 0;
|
|
582
|
+
if (CONTROL_BLOCK.test(header)) continue;
|
|
583
|
+
const name = nameIn(header);
|
|
584
|
+
if (name) return name;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/source.ts
|
|
474
592
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
475
593
|
var MAX_CACHED_FILES = 50;
|
|
476
594
|
var MAPPING_URL = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
|
|
477
595
|
var cache = /* @__PURE__ */ new Map();
|
|
478
|
-
async function enrichFrames(frames, cwd = process.cwd()) {
|
|
596
|
+
async function enrichFrames(frames, cwd = process.cwd(), options = {}) {
|
|
479
597
|
const result = [];
|
|
480
598
|
let withContext = 0;
|
|
481
599
|
for (const frame of frames) {
|
|
482
600
|
try {
|
|
483
|
-
const { frame:
|
|
601
|
+
const { frame: located, lines, original } = await mapFrame(frame, options.mapLocator);
|
|
602
|
+
const named = options.inferFunctionNames && original && !located.fn && lines && located.line ? withInferredName(located, lines) : located;
|
|
603
|
+
const mapped = named.inApp && isLibraryPath(named.file) ? { ...named, inApp: false } : named;
|
|
484
604
|
let next = mapped;
|
|
485
605
|
if (mapped.inApp && lines && mapped.line && withContext < LIMITS.contextFrames) {
|
|
486
606
|
const context = contextFor(lines, mapped.line);
|
|
@@ -496,29 +616,36 @@ async function enrichFrames(frames, cwd = process.cwd()) {
|
|
|
496
616
|
}
|
|
497
617
|
return result;
|
|
498
618
|
}
|
|
499
|
-
async function enrichException(exception, cwd = process.cwd()) {
|
|
500
|
-
const frames = await enrichFrames(exception.frames, cwd);
|
|
619
|
+
async function enrichException(exception, cwd = process.cwd(), options = {}) {
|
|
620
|
+
const frames = await enrichFrames(exception.frames, cwd, options);
|
|
501
621
|
if (!exception.cause) return { ...exception, frames };
|
|
502
|
-
return { ...exception, frames, cause: await enrichException(exception.cause, cwd) };
|
|
622
|
+
return { ...exception, frames, cause: await enrichException(exception.cause, cwd, options) };
|
|
623
|
+
}
|
|
624
|
+
function withInferredName(frame, lines) {
|
|
625
|
+
const fn = enclosingFunctionName(lines, frame.line ?? 0, frame.col);
|
|
626
|
+
return fn ? { ...frame, fn } : frame;
|
|
627
|
+
}
|
|
628
|
+
function isLibraryPath(file) {
|
|
629
|
+
return file.includes("/node_modules/") || file.includes("\\node_modules\\");
|
|
503
630
|
}
|
|
504
631
|
function relativize(file, cwd) {
|
|
505
632
|
if (!(0, import_node_path.isAbsolute)(file)) return file;
|
|
506
633
|
const path = (0, import_node_path.relative)(cwd, file);
|
|
507
634
|
return path.startsWith("..") || (0, import_node_path.isAbsolute)(path) ? file : path.split(import_node_path.sep).join("/");
|
|
508
635
|
}
|
|
509
|
-
async function mapFrame(frame) {
|
|
510
|
-
if (!frame.line || !(0, import_node_path.isAbsolute)(frame.file)) return { frame, lines: null };
|
|
511
|
-
const file = await loadFile(frame.file);
|
|
512
|
-
if (!file) return { frame, lines: null };
|
|
513
|
-
if (!file.map) return { frame, lines: file.lines };
|
|
636
|
+
async function mapFrame(frame, locator) {
|
|
637
|
+
if (!frame.line || !(0, import_node_path.isAbsolute)(frame.file)) return { frame, lines: null, original: false };
|
|
638
|
+
const file = await loadFile(frame.file, locator);
|
|
639
|
+
if (!file) return { frame, lines: null, original: false };
|
|
640
|
+
if (!file.map) return { frame, lines: file.lines, original: false };
|
|
514
641
|
const entry = file.map.map.findEntry(frame.line - 1, (frame.col ?? 1) - 1);
|
|
515
|
-
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines };
|
|
642
|
+
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines, original: false };
|
|
516
643
|
const originalPath = resolveSource(file.map, entry.originalSource);
|
|
517
644
|
const mapped = { ...frame, file: originalPath, line: entry.originalLine + 1, col: (entry.originalColumn ?? 0) + 1 };
|
|
518
645
|
const lines = embeddedLines(file.map, file.map.sources.indexOf(entry.originalSource));
|
|
519
|
-
if (lines) return { frame: mapped, lines };
|
|
646
|
+
if (lines) return { frame: mapped, lines, original: true };
|
|
520
647
|
const original = await loadFile(originalPath);
|
|
521
|
-
return { frame: mapped, lines: original?.lines ?? null };
|
|
648
|
+
return { frame: mapped, lines: original?.lines ?? null, original: true };
|
|
522
649
|
}
|
|
523
650
|
function embeddedLines(map, index) {
|
|
524
651
|
if (index < 0) return null;
|
|
@@ -550,26 +677,27 @@ function resolveSource(map, source) {
|
|
|
550
677
|
}
|
|
551
678
|
return (0, import_node_path.isAbsolute)(source) ? source : (0, import_node_path.resolve)(map.baseDir, source);
|
|
552
679
|
}
|
|
553
|
-
function loadFile(path) {
|
|
554
|
-
const
|
|
680
|
+
function loadFile(path, locator) {
|
|
681
|
+
const key = locator ? `${path}\0located` : path;
|
|
682
|
+
const cached = cache.get(key);
|
|
555
683
|
if (cached) {
|
|
556
|
-
cache.delete(
|
|
557
|
-
cache.set(
|
|
684
|
+
cache.delete(key);
|
|
685
|
+
cache.set(key, cached);
|
|
558
686
|
return cached;
|
|
559
687
|
}
|
|
560
|
-
const pending = readSource(path).catch(() => null);
|
|
561
|
-
cache.set(
|
|
688
|
+
const pending = readSource(path, locator).catch(() => null);
|
|
689
|
+
cache.set(key, pending);
|
|
562
690
|
if (cache.size > MAX_CACHED_FILES) {
|
|
563
691
|
const oldest = cache.keys().next().value;
|
|
564
692
|
if (oldest !== void 0) cache.delete(oldest);
|
|
565
693
|
}
|
|
566
694
|
return pending;
|
|
567
695
|
}
|
|
568
|
-
async function readSource(path) {
|
|
696
|
+
async function readSource(path, locator) {
|
|
569
697
|
const text = await readText(path);
|
|
570
698
|
if (text === null) return null;
|
|
571
699
|
const lines = text.split(/\r?\n/);
|
|
572
|
-
return { lines, map: await loadMap(path, lines) };
|
|
700
|
+
return { lines, map: await loadMap(path, lines, locator) };
|
|
573
701
|
}
|
|
574
702
|
async function readText(path) {
|
|
575
703
|
try {
|
|
@@ -580,7 +708,7 @@ async function readText(path) {
|
|
|
580
708
|
return null;
|
|
581
709
|
}
|
|
582
710
|
}
|
|
583
|
-
async function loadMap(path, lines) {
|
|
711
|
+
async function loadMap(path, lines, locator) {
|
|
584
712
|
let url;
|
|
585
713
|
for (let index = lines.length - 1; index >= 0 && index >= lines.length - 5; index--) {
|
|
586
714
|
const match = MAPPING_URL.exec(lines[index] ?? "");
|
|
@@ -589,10 +717,15 @@ async function loadMap(path, lines) {
|
|
|
589
717
|
break;
|
|
590
718
|
}
|
|
591
719
|
}
|
|
592
|
-
if (!url) return null;
|
|
593
720
|
let raw;
|
|
594
721
|
let mapDir;
|
|
595
|
-
|
|
722
|
+
const located = url ? null : locator ? locator(path) : `${path}.map`;
|
|
723
|
+
if (located) {
|
|
724
|
+
raw = await readText(located);
|
|
725
|
+
mapDir = (0, import_node_path.dirname)(located);
|
|
726
|
+
} else if (!url) {
|
|
727
|
+
return null;
|
|
728
|
+
} else if (url.startsWith("data:")) {
|
|
596
729
|
const comma = url.indexOf(",");
|
|
597
730
|
const encoded = url.slice(comma + 1);
|
|
598
731
|
raw = url.slice(0, comma).endsWith(";base64") ? Buffer.from(encoded, "base64").toString("utf8") : decodeURIComponent(encoded);
|
|
@@ -621,12 +754,13 @@ async function loadMap(path, lines) {
|
|
|
621
754
|
var import_node_url2 = require("url");
|
|
622
755
|
var WITH_FUNCTION = /^at (?:async )?(.+?) \((.+)\)$/;
|
|
623
756
|
var LOCATION = /^(.+):(\d+):(\d+)$/;
|
|
757
|
+
var MAX_FRAME_LINE = 1e3;
|
|
624
758
|
function parseStack(stack) {
|
|
625
759
|
if (!stack) return [];
|
|
626
760
|
const frames = [];
|
|
627
761
|
for (const raw of stack.split("\n")) {
|
|
628
762
|
const line = raw.trim();
|
|
629
|
-
if (!line.startsWith("at ")) continue;
|
|
763
|
+
if (line.length > MAX_FRAME_LINE || !line.startsWith("at ")) continue;
|
|
630
764
|
const withFunction = WITH_FUNCTION.exec(line);
|
|
631
765
|
const fn = withFunction?.[1];
|
|
632
766
|
const location = withFunction ? withFunction[2] ?? "" : line.replace(/^at (?:async )?/, "");
|
|
@@ -721,6 +855,7 @@ var Transport = class {
|
|
|
721
855
|
inFlight = null;
|
|
722
856
|
nextAttemptAt = 0;
|
|
723
857
|
backoffMs = 0;
|
|
858
|
+
unauthorizedUntil = 0;
|
|
724
859
|
stopped = false;
|
|
725
860
|
warned = /* @__PURE__ */ new Set();
|
|
726
861
|
timer;
|
|
@@ -810,10 +945,8 @@ var Transport = class {
|
|
|
810
945
|
this.nextAttemptAt = Date.now() + Math.max(this.backoffMs, outcome.retryAfterMs ?? 0);
|
|
811
946
|
return false;
|
|
812
947
|
case "unauthorized":
|
|
813
|
-
this.droppedCount += events.length
|
|
814
|
-
this.
|
|
815
|
-
this.nextAttemptAt = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
816
|
-
this.warn("unauthorized", `chave do Radar recusada (${outcome.status}); envios pausados por 10 minutos`, true);
|
|
948
|
+
this.droppedCount += events.length;
|
|
949
|
+
this.pauseUnauthorized(outcome.status);
|
|
817
950
|
return false;
|
|
818
951
|
case "rejected":
|
|
819
952
|
this.droppedCount += events.length;
|
|
@@ -821,6 +954,47 @@ var Transport = class {
|
|
|
821
954
|
return true;
|
|
822
955
|
}
|
|
823
956
|
}
|
|
957
|
+
async checkIn(slug, payload) {
|
|
958
|
+
if (this.stopped || !this.config.checkInBaseUrl || Date.now() < this.unauthorizedUntil) return;
|
|
959
|
+
let response;
|
|
960
|
+
try {
|
|
961
|
+
response = await fetch(this.config.checkInBaseUrl + encodeURIComponent(slug), {
|
|
962
|
+
method: "POST",
|
|
963
|
+
headers: this.headers(),
|
|
964
|
+
body: JSON.stringify(payload),
|
|
965
|
+
redirect: "error",
|
|
966
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
|
|
967
|
+
});
|
|
968
|
+
} catch (error) {
|
|
969
|
+
this.warn("checkin_network", `check-in no Radar falhou: ${describeError(error)}`, false);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
await response.arrayBuffer().catch(() => void 0);
|
|
973
|
+
if (response.ok) return;
|
|
974
|
+
if (response.status === 401 || response.status === 403) {
|
|
975
|
+
this.pauseUnauthorized(response.status);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (response.status === 404) {
|
|
979
|
+
this.warn(`checkin_unknown_${slug}`, `tarefa agendada "${slug}" n\xE3o existe no projeto da chave; crie o monitor com esse slug no Radar`, true);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this.warn(`checkin_rejected_${response.status}`, `check-in recusado pelo Radar (${response.status})`, false);
|
|
983
|
+
}
|
|
984
|
+
pauseUnauthorized(status) {
|
|
985
|
+
this.droppedCount += this.queue.length;
|
|
986
|
+
this.queue = [];
|
|
987
|
+
this.unauthorizedUntil = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
988
|
+
this.nextAttemptAt = Math.max(this.nextAttemptAt, this.unauthorizedUntil);
|
|
989
|
+
this.warn("unauthorized", `chave do Radar recusada (${status}); envios pausados por 10 minutos`, true);
|
|
990
|
+
}
|
|
991
|
+
headers() {
|
|
992
|
+
return {
|
|
993
|
+
"content-type": "application/json",
|
|
994
|
+
authorization: `Bearer ${this.config.key}`,
|
|
995
|
+
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
996
|
+
};
|
|
997
|
+
}
|
|
824
998
|
requeue(events) {
|
|
825
999
|
this.queue.unshift(...events);
|
|
826
1000
|
const overflow = this.queue.length - this.maxQueue;
|
|
@@ -832,11 +1006,7 @@ var Transport = class {
|
|
|
832
1006
|
async post(batch) {
|
|
833
1007
|
const json = JSON.stringify(batch);
|
|
834
1008
|
const compress = Buffer.byteLength(json) > GZIP_THRESHOLD_BYTES;
|
|
835
|
-
const headers =
|
|
836
|
-
"content-type": "application/json",
|
|
837
|
-
authorization: `Bearer ${this.config.key}`,
|
|
838
|
-
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
839
|
-
};
|
|
1009
|
+
const headers = this.headers();
|
|
840
1010
|
if (compress) headers["content-encoding"] = "gzip";
|
|
841
1011
|
let response;
|
|
842
1012
|
try {
|
|
@@ -890,9 +1060,12 @@ function withTimeout(promise, ms) {
|
|
|
890
1060
|
|
|
891
1061
|
// src/version.ts
|
|
892
1062
|
var SDK_NAME = "@oconde/radar";
|
|
893
|
-
var SDK_VERSION = true ? "0.
|
|
1063
|
+
var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
894
1064
|
|
|
895
1065
|
// src/client.ts
|
|
1066
|
+
var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
|
|
1067
|
+
var MAX_CHECK_IN_ENVIRONMENT = 64;
|
|
1068
|
+
var MAX_CHECK_IN_RELEASE = 128;
|
|
896
1069
|
var RadarClient = class {
|
|
897
1070
|
options = resolveOptions({}, {});
|
|
898
1071
|
transport = null;
|
|
@@ -921,6 +1094,7 @@ var RadarClient = class {
|
|
|
921
1094
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
922
1095
|
environment: this.options.environment,
|
|
923
1096
|
...this.options.release ? { release: this.options.release } : {},
|
|
1097
|
+
checkInBaseUrl: this.options.endpoint + CHECKIN_PATH,
|
|
924
1098
|
onWarning: (message, always) => always ? this.writeWarning(message) : this.debugWarn(message)
|
|
925
1099
|
});
|
|
926
1100
|
}
|
|
@@ -982,6 +1156,51 @@ var RadarClient = class {
|
|
|
982
1156
|
if (currentContext()) return execute();
|
|
983
1157
|
return runWithContext({ requestId: (0, import_node_crypto3.randomUUID)(), startedAt: import_node_perf_hooks3.performance.now() }, execute);
|
|
984
1158
|
}
|
|
1159
|
+
withContext(fn, options = {}) {
|
|
1160
|
+
return runWithContext({ requestId: requestIdFrom(options.requestId), startedAt: import_node_perf_hooks3.performance.now() }, fn);
|
|
1161
|
+
}
|
|
1162
|
+
async checkIn(slug, status, options = {}) {
|
|
1163
|
+
try {
|
|
1164
|
+
if (!this.transport || this.closed) return;
|
|
1165
|
+
if (typeof slug !== "string" || !CHECKIN_SLUG.test(slug)) {
|
|
1166
|
+
this.debugWarn(`slug de tarefa agendada inv\xE1lido: ${String(slug).slice(0, 80)}`);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (!CHECKIN_STATUSES.includes(status)) return;
|
|
1170
|
+
const checkInId = typeof options.checkInId === "string" && options.checkInId ? options.checkInId.slice(0, LIMITS.checkInIdLength) : void 0;
|
|
1171
|
+
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;
|
|
1172
|
+
const environment = this.options.environment.slice(0, MAX_CHECK_IN_ENVIRONMENT) || "production";
|
|
1173
|
+
const release = this.options.release?.slice(0, MAX_CHECK_IN_RELEASE);
|
|
1174
|
+
const payload = {
|
|
1175
|
+
status,
|
|
1176
|
+
...checkInId ? { checkInId } : {},
|
|
1177
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1178
|
+
environment,
|
|
1179
|
+
...release ? { release } : {}
|
|
1180
|
+
};
|
|
1181
|
+
await this.transport.checkIn(slug, payload);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
this.debugWarn(`check-in falhou: ${describeError(error)}`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async cron(slug, fn) {
|
|
1187
|
+
const checkInId = (0, import_node_crypto3.randomUUID)();
|
|
1188
|
+
const started = import_node_perf_hooks3.performance.now();
|
|
1189
|
+
const execute = async () => {
|
|
1190
|
+
await this.checkIn(slug, "in_progress", { checkInId });
|
|
1191
|
+
try {
|
|
1192
|
+
const result = await fn();
|
|
1193
|
+
await this.checkIn(slug, "ok", { checkInId, durationMs: elapsedSince(started) });
|
|
1194
|
+
return result;
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
this.captureError(error, { cron: slug });
|
|
1197
|
+
await this.checkIn(slug, "error", { checkInId, durationMs: elapsedSince(started) });
|
|
1198
|
+
throw error;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
if (currentContext()) return execute();
|
|
1202
|
+
return this.withContext(execute);
|
|
1203
|
+
}
|
|
985
1204
|
middleware() {
|
|
986
1205
|
return createMiddleware(this);
|
|
987
1206
|
}
|
|
@@ -999,7 +1218,7 @@ var RadarClient = class {
|
|
|
999
1218
|
() => this.emitLog(level, "http.request", {
|
|
1000
1219
|
method: (req.method ?? "GET").toUpperCase(),
|
|
1001
1220
|
...route ? { route } : {},
|
|
1002
|
-
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), req
|
|
1221
|
+
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
|
|
1003
1222
|
status,
|
|
1004
1223
|
durationMs: elapsedSince(context.startedAt)
|
|
1005
1224
|
})
|
|
@@ -1036,31 +1255,40 @@ var RadarClient = class {
|
|
|
1036
1255
|
this.print(event);
|
|
1037
1256
|
this.transport?.enqueue(event);
|
|
1038
1257
|
}
|
|
1258
|
+
captureException(exception, options = {}) {
|
|
1259
|
+
this.safely(() => this.emitException(exception, options));
|
|
1260
|
+
}
|
|
1039
1261
|
emitError(error, data, level, handled) {
|
|
1040
1262
|
if (this.closed) return;
|
|
1041
1263
|
if (typeof error === "object" && error !== null) {
|
|
1042
1264
|
if (this.captured.has(error)) return;
|
|
1043
1265
|
this.captured.add(error);
|
|
1044
1266
|
}
|
|
1267
|
+
this.emitException(exceptionInfo(error), { level, handled, ...data !== void 0 ? { attrs: data } : {} }, error instanceof Error ? error.stack : void 0);
|
|
1268
|
+
}
|
|
1269
|
+
emitException(exception, options, stack) {
|
|
1270
|
+
if (this.closed) return;
|
|
1045
1271
|
const context = currentContext();
|
|
1046
1272
|
const mode = this.options.redact;
|
|
1273
|
+
const request = options.request === null ? void 0 : options.request ?? (context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0);
|
|
1274
|
+
const runtime = options.runtime === void 0 ? runtimeInfo() : options.runtime;
|
|
1047
1275
|
const event = {
|
|
1048
1276
|
type: "error",
|
|
1049
1277
|
ts: Date.now(),
|
|
1050
|
-
level,
|
|
1051
|
-
handled,
|
|
1052
|
-
exception
|
|
1278
|
+
level: options.level ?? "error",
|
|
1279
|
+
handled: options.handled ?? true,
|
|
1280
|
+
exception,
|
|
1053
1281
|
...context ? { requestId: context.requestId } : {},
|
|
1054
|
-
...
|
|
1282
|
+
...request ? { request } : {},
|
|
1055
1283
|
...context?.user ? { user: context.user } : {},
|
|
1056
|
-
runtime:
|
|
1057
|
-
...
|
|
1284
|
+
...runtime ? { runtime } : {},
|
|
1285
|
+
...options.attrs !== void 0 ? { attrs: prepareAttrs(options.attrs, mode) } : {}
|
|
1058
1286
|
};
|
|
1059
|
-
this.print(event,
|
|
1287
|
+
this.print(event, stack);
|
|
1060
1288
|
if (!this.transport) return;
|
|
1061
1289
|
this.transport.enqueue(
|
|
1062
|
-
() => enrichException(event.exception).then(
|
|
1063
|
-
(
|
|
1290
|
+
() => enrichException(event.exception, process.cwd(), options.enrich).then(
|
|
1291
|
+
(enriched) => ({ ...event, exception: enriched }),
|
|
1064
1292
|
() => event
|
|
1065
1293
|
)
|
|
1066
1294
|
);
|