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