@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.js
CHANGED
|
@@ -5,29 +5,13 @@ import { performance as performance3 } from "perf_hooks";
|
|
|
5
5
|
|
|
6
6
|
// src/context.ts
|
|
7
7
|
import { AsyncLocalStorage } from "async_hooks";
|
|
8
|
-
var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
|
|
9
|
-
function globalState() {
|
|
10
|
-
const holder = globalThis;
|
|
11
|
-
let state2 = holder[STATE_KEY];
|
|
12
|
-
if (!state2) {
|
|
13
|
-
state2 = { storage: new AsyncLocalStorage() };
|
|
14
|
-
holder[STATE_KEY] = state2;
|
|
15
|
-
}
|
|
16
|
-
return state2;
|
|
17
|
-
}
|
|
18
|
-
function currentContext() {
|
|
19
|
-
return globalState().storage.getStore();
|
|
20
|
-
}
|
|
21
|
-
function runWithContext(context, fn) {
|
|
22
|
-
return globalState().storage.run(context, fn);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// src/express.ts
|
|
26
8
|
import { randomUUID } from "crypto";
|
|
27
|
-
import { performance } from "perf_hooks";
|
|
28
9
|
|
|
29
10
|
// src/protocol/index.ts
|
|
30
11
|
var INGEST_PATH = "/api/v1/events";
|
|
12
|
+
var CHECKIN_PATH = "/api/v1/checkins/";
|
|
13
|
+
var CHECKIN_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
14
|
+
var CHECKIN_STATUSES = ["in_progress", "ok", "error"];
|
|
31
15
|
var LIMITS = {
|
|
32
16
|
batchBytes: 1e6,
|
|
33
17
|
batchEvents: 500,
|
|
@@ -43,9 +27,36 @@ var LIMITS = {
|
|
|
43
27
|
causeDepth: 5,
|
|
44
28
|
depth: 6,
|
|
45
29
|
arrayItems: 50,
|
|
46
|
-
requestIdLength: 128
|
|
30
|
+
requestIdLength: 128,
|
|
31
|
+
checkInIdLength: 64
|
|
47
32
|
};
|
|
48
33
|
|
|
34
|
+
// src/context.ts
|
|
35
|
+
var STATE_KEY = /* @__PURE__ */ Symbol.for("@oconde/radar");
|
|
36
|
+
function globalState() {
|
|
37
|
+
const holder = globalThis;
|
|
38
|
+
let state2 = holder[STATE_KEY];
|
|
39
|
+
if (!state2) {
|
|
40
|
+
state2 = { storage: new 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
|
+
var SAFE_REQUEST_ID = /^[\x21-\x7e]+$/;
|
|
52
|
+
function requestIdFrom(incoming) {
|
|
53
|
+
const value = incoming?.trim();
|
|
54
|
+
return value && value.length <= LIMITS.requestIdLength && SAFE_REQUEST_ID.test(value) ? value : randomUUID();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/express.ts
|
|
58
|
+
import { performance } from "perf_hooks";
|
|
59
|
+
|
|
49
60
|
// src/redact.ts
|
|
50
61
|
import { createHash, createHmac, randomBytes } from "crypto";
|
|
51
62
|
var SECRET_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -246,6 +257,40 @@ function requestRoute(req, route) {
|
|
|
246
257
|
if (route) return route;
|
|
247
258
|
return typeof req.route?.path === "string" ? req.route.path : void 0;
|
|
248
259
|
}
|
|
260
|
+
var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
|
|
261
|
+
function pathSegments(value) {
|
|
262
|
+
const segments = value.split("/");
|
|
263
|
+
while (segments.length > 1 && segments[segments.length - 1] === "") segments.pop();
|
|
264
|
+
return segments;
|
|
265
|
+
}
|
|
266
|
+
function decodeSegment(value) {
|
|
267
|
+
try {
|
|
268
|
+
return decodeURIComponent(value);
|
|
269
|
+
} catch {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function routeParams(req, path) {
|
|
274
|
+
if (typeof req.route?.path !== "string") return {};
|
|
275
|
+
const pattern = pathSegments(`${typeof req.baseUrl === "string" ? req.baseUrl : ""}${req.route.path}`).filter((segment, index) => index > 0 || segment !== "");
|
|
276
|
+
const values = pathSegments(path);
|
|
277
|
+
if (pattern.length > values.length) return {};
|
|
278
|
+
const offset = values.length - pattern.length;
|
|
279
|
+
const result = {};
|
|
280
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
281
|
+
const value = values[offset + index];
|
|
282
|
+
const param = ROUTE_PARAM.exec(pattern[index]);
|
|
283
|
+
if (!param) {
|
|
284
|
+
if (pattern[index] !== value) return {};
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (value) result[param[1]] = decodeSegment(value);
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
function pathParams(req, path) {
|
|
292
|
+
return { ...routeParams(req, path), ...req.params ?? {} };
|
|
293
|
+
}
|
|
249
294
|
function redactPath(path, params, mode) {
|
|
250
295
|
if (mode === "none" || !params) return path;
|
|
251
296
|
let result = path;
|
|
@@ -261,7 +306,7 @@ function safeUrl(req, mode) {
|
|
|
261
306
|
const queryStart = url.indexOf("?");
|
|
262
307
|
const path = queryStart >= 0 ? url.slice(0, queryStart) : url;
|
|
263
308
|
const query = queryStart >= 0 ? url.slice(queryStart) : "";
|
|
264
|
-
return redactUrl(redactPath(path, req
|
|
309
|
+
return redactUrl(redactPath(path, pathParams(req, path), mode) + query, mode);
|
|
265
310
|
}
|
|
266
311
|
function redactUrl(url, mode) {
|
|
267
312
|
const queryStart = url.indexOf("?");
|
|
@@ -329,8 +374,7 @@ function createMiddleware(logger) {
|
|
|
329
374
|
return (req, res, next) => {
|
|
330
375
|
let context;
|
|
331
376
|
try {
|
|
332
|
-
const
|
|
333
|
-
const requestId = incoming && incoming.length <= LIMITS.requestIdLength ? incoming : randomUUID();
|
|
377
|
+
const requestId = requestIdFrom(headerValue(req.headers["x-request-id"]));
|
|
334
378
|
context = { requestId, startedAt: performance.now(), req };
|
|
335
379
|
res.setHeader("x-request-id", requestId);
|
|
336
380
|
if (logger.shouldLogRequest(req)) res.once("finish", () => logger.logHttpRequest(context, res.statusCode));
|
|
@@ -421,16 +465,92 @@ import { readFile, stat } from "fs/promises";
|
|
|
421
465
|
import { SourceMap } from "module";
|
|
422
466
|
import { dirname, isAbsolute, relative, resolve, sep } from "path";
|
|
423
467
|
import { fileURLToPath } from "url";
|
|
468
|
+
|
|
469
|
+
// src/function-name.ts
|
|
470
|
+
var IDENTIFIER = "[A-Za-z_$][\\w$]*";
|
|
471
|
+
var MAX_LINES_UP = 400;
|
|
472
|
+
var MAX_HEADER_LINES = 6;
|
|
473
|
+
var MAX_NAME_LENGTH = 100;
|
|
474
|
+
var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
|
|
475
|
+
var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
|
|
476
|
+
var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
|
|
477
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "await", "else", "do", "try"]);
|
|
478
|
+
var NAME_PATTERNS = [
|
|
479
|
+
new RegExp(`\\bfunction\\s*\\*?\\s*(${IDENTIFIER})\\s*\\(`),
|
|
480
|
+
new RegExp(`(${IDENTIFIER})\\s*[:=]\\s*(?:async\\s+)?function\\b`),
|
|
481
|
+
new RegExp(`(${IDENTIFIER})\\s*(?::\\s*[^=]+)?=\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`),
|
|
482
|
+
new RegExp(`(?:^|[{,(]\\s*)(${IDENTIFIER})\\s*:\\s*(?:async\\s+)?(?:\\([^]*\\)|${IDENTIFIER})\\s*(?::\\s*[^=]+)?=>\\s*$`)
|
|
483
|
+
];
|
|
484
|
+
var METHOD_PATTERN = new RegExp(`^\\s*(?:(?:export|default|public|private|protected|static|async|override|readonly|get|set)\\s+)*\\*?\\s*(${IDENTIFIER})\\s*(?:<[^>]*>)?\\s*\\([^]*\\)\\s*(?::\\s*[^{]+)?$`);
|
|
485
|
+
var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
|
|
486
|
+
function codeOnly(text) {
|
|
487
|
+
return text.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, '""').replace(/\/\*.*?\*\//g, "").replace(/(^|[^:])\/\/.*$/, "$1");
|
|
488
|
+
}
|
|
489
|
+
function parenBalance(text) {
|
|
490
|
+
let balance = 0;
|
|
491
|
+
for (const char of text) {
|
|
492
|
+
if (char === "(") balance += 1;
|
|
493
|
+
else if (char === ")") balance -= 1;
|
|
494
|
+
}
|
|
495
|
+
return balance;
|
|
496
|
+
}
|
|
497
|
+
function headerOf(lines, index, prefix) {
|
|
498
|
+
let header = prefix;
|
|
499
|
+
for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
|
|
500
|
+
if (header.trim() !== "" && parenBalance(header) >= 0) break;
|
|
501
|
+
header = `${codeOnly(lines[previous] ?? "")} ${header}`;
|
|
502
|
+
}
|
|
503
|
+
return header;
|
|
504
|
+
}
|
|
505
|
+
function nameIn(header) {
|
|
506
|
+
const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
|
|
507
|
+
for (const pattern of patterns) {
|
|
508
|
+
const name = pattern.exec(header)?.[1];
|
|
509
|
+
if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
|
|
510
|
+
}
|
|
511
|
+
return void 0;
|
|
512
|
+
}
|
|
513
|
+
function enclosingFunctionName(lines, line, column) {
|
|
514
|
+
const start = line - 1;
|
|
515
|
+
if (start < 0 || start >= lines.length) return void 0;
|
|
516
|
+
let depth = 0;
|
|
517
|
+
for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
|
|
518
|
+
const raw = lines[index] ?? "";
|
|
519
|
+
if (index < start && MARKUP_BOUNDARY.test(raw)) return void 0;
|
|
520
|
+
const code = codeOnly(index === start && column ? raw.slice(0, Math.max(0, column - 1)) : raw);
|
|
521
|
+
for (let position = code.length - 1; position >= 0; position--) {
|
|
522
|
+
const char = code[position];
|
|
523
|
+
if (char === "}") {
|
|
524
|
+
depth += 1;
|
|
525
|
+
} else if (char === "{") {
|
|
526
|
+
if (depth > 0) {
|
|
527
|
+
depth -= 1;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const header = headerOf(lines, index, code.slice(0, position));
|
|
531
|
+
if (CLASS_BODY.test(header)) return void 0;
|
|
532
|
+
if (CONTROL_BLOCK.test(header)) continue;
|
|
533
|
+
const name = nameIn(header);
|
|
534
|
+
if (name) return name;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return void 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/source.ts
|
|
424
542
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
425
543
|
var MAX_CACHED_FILES = 50;
|
|
426
544
|
var MAPPING_URL = /\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;
|
|
427
545
|
var cache = /* @__PURE__ */ new Map();
|
|
428
|
-
async function enrichFrames(frames, cwd = process.cwd()) {
|
|
546
|
+
async function enrichFrames(frames, cwd = process.cwd(), options = {}) {
|
|
429
547
|
const result = [];
|
|
430
548
|
let withContext = 0;
|
|
431
549
|
for (const frame of frames) {
|
|
432
550
|
try {
|
|
433
|
-
const { frame:
|
|
551
|
+
const { frame: located, lines, original } = await mapFrame(frame, options.mapLocator);
|
|
552
|
+
const named = options.inferFunctionNames && original && !located.fn && lines && located.line ? withInferredName(located, lines) : located;
|
|
553
|
+
const mapped = named.inApp && isLibraryPath(named.file) ? { ...named, inApp: false } : named;
|
|
434
554
|
let next = mapped;
|
|
435
555
|
if (mapped.inApp && lines && mapped.line && withContext < LIMITS.contextFrames) {
|
|
436
556
|
const context = contextFor(lines, mapped.line);
|
|
@@ -446,29 +566,36 @@ async function enrichFrames(frames, cwd = process.cwd()) {
|
|
|
446
566
|
}
|
|
447
567
|
return result;
|
|
448
568
|
}
|
|
449
|
-
async function enrichException(exception, cwd = process.cwd()) {
|
|
450
|
-
const frames = await enrichFrames(exception.frames, cwd);
|
|
569
|
+
async function enrichException(exception, cwd = process.cwd(), options = {}) {
|
|
570
|
+
const frames = await enrichFrames(exception.frames, cwd, options);
|
|
451
571
|
if (!exception.cause) return { ...exception, frames };
|
|
452
|
-
return { ...exception, frames, cause: await enrichException(exception.cause, cwd) };
|
|
572
|
+
return { ...exception, frames, cause: await enrichException(exception.cause, cwd, options) };
|
|
573
|
+
}
|
|
574
|
+
function withInferredName(frame, lines) {
|
|
575
|
+
const fn = enclosingFunctionName(lines, frame.line ?? 0, frame.col);
|
|
576
|
+
return fn ? { ...frame, fn } : frame;
|
|
577
|
+
}
|
|
578
|
+
function isLibraryPath(file) {
|
|
579
|
+
return file.includes("/node_modules/") || file.includes("\\node_modules\\");
|
|
453
580
|
}
|
|
454
581
|
function relativize(file, cwd) {
|
|
455
582
|
if (!isAbsolute(file)) return file;
|
|
456
583
|
const path = relative(cwd, file);
|
|
457
584
|
return path.startsWith("..") || isAbsolute(path) ? file : path.split(sep).join("/");
|
|
458
585
|
}
|
|
459
|
-
async function mapFrame(frame) {
|
|
460
|
-
if (!frame.line || !isAbsolute(frame.file)) return { frame, lines: null };
|
|
461
|
-
const file = await loadFile(frame.file);
|
|
462
|
-
if (!file) return { frame, lines: null };
|
|
463
|
-
if (!file.map) return { frame, lines: file.lines };
|
|
586
|
+
async function mapFrame(frame, locator) {
|
|
587
|
+
if (!frame.line || !isAbsolute(frame.file)) return { frame, lines: null, original: false };
|
|
588
|
+
const file = await loadFile(frame.file, locator);
|
|
589
|
+
if (!file) return { frame, lines: null, original: false };
|
|
590
|
+
if (!file.map) return { frame, lines: file.lines, original: false };
|
|
464
591
|
const entry = file.map.map.findEntry(frame.line - 1, (frame.col ?? 1) - 1);
|
|
465
|
-
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines };
|
|
592
|
+
if (typeof entry.originalSource !== "string" || typeof entry.originalLine !== "number") return { frame, lines: file.lines, original: false };
|
|
466
593
|
const originalPath = resolveSource(file.map, entry.originalSource);
|
|
467
594
|
const mapped = { ...frame, file: originalPath, line: entry.originalLine + 1, col: (entry.originalColumn ?? 0) + 1 };
|
|
468
595
|
const lines = embeddedLines(file.map, file.map.sources.indexOf(entry.originalSource));
|
|
469
|
-
if (lines) return { frame: mapped, lines };
|
|
596
|
+
if (lines) return { frame: mapped, lines, original: true };
|
|
470
597
|
const original = await loadFile(originalPath);
|
|
471
|
-
return { frame: mapped, lines: original?.lines ?? null };
|
|
598
|
+
return { frame: mapped, lines: original?.lines ?? null, original: true };
|
|
472
599
|
}
|
|
473
600
|
function embeddedLines(map, index) {
|
|
474
601
|
if (index < 0) return null;
|
|
@@ -500,26 +627,27 @@ function resolveSource(map, source) {
|
|
|
500
627
|
}
|
|
501
628
|
return isAbsolute(source) ? source : resolve(map.baseDir, source);
|
|
502
629
|
}
|
|
503
|
-
function loadFile(path) {
|
|
504
|
-
const
|
|
630
|
+
function loadFile(path, locator) {
|
|
631
|
+
const key = locator ? `${path}\0located` : path;
|
|
632
|
+
const cached = cache.get(key);
|
|
505
633
|
if (cached) {
|
|
506
|
-
cache.delete(
|
|
507
|
-
cache.set(
|
|
634
|
+
cache.delete(key);
|
|
635
|
+
cache.set(key, cached);
|
|
508
636
|
return cached;
|
|
509
637
|
}
|
|
510
|
-
const pending = readSource(path).catch(() => null);
|
|
511
|
-
cache.set(
|
|
638
|
+
const pending = readSource(path, locator).catch(() => null);
|
|
639
|
+
cache.set(key, pending);
|
|
512
640
|
if (cache.size > MAX_CACHED_FILES) {
|
|
513
641
|
const oldest = cache.keys().next().value;
|
|
514
642
|
if (oldest !== void 0) cache.delete(oldest);
|
|
515
643
|
}
|
|
516
644
|
return pending;
|
|
517
645
|
}
|
|
518
|
-
async function readSource(path) {
|
|
646
|
+
async function readSource(path, locator) {
|
|
519
647
|
const text = await readText(path);
|
|
520
648
|
if (text === null) return null;
|
|
521
649
|
const lines = text.split(/\r?\n/);
|
|
522
|
-
return { lines, map: await loadMap(path, lines) };
|
|
650
|
+
return { lines, map: await loadMap(path, lines, locator) };
|
|
523
651
|
}
|
|
524
652
|
async function readText(path) {
|
|
525
653
|
try {
|
|
@@ -530,7 +658,7 @@ async function readText(path) {
|
|
|
530
658
|
return null;
|
|
531
659
|
}
|
|
532
660
|
}
|
|
533
|
-
async function loadMap(path, lines) {
|
|
661
|
+
async function loadMap(path, lines, locator) {
|
|
534
662
|
let url;
|
|
535
663
|
for (let index = lines.length - 1; index >= 0 && index >= lines.length - 5; index--) {
|
|
536
664
|
const match = MAPPING_URL.exec(lines[index] ?? "");
|
|
@@ -539,10 +667,15 @@ async function loadMap(path, lines) {
|
|
|
539
667
|
break;
|
|
540
668
|
}
|
|
541
669
|
}
|
|
542
|
-
if (!url) return null;
|
|
543
670
|
let raw;
|
|
544
671
|
let mapDir;
|
|
545
|
-
|
|
672
|
+
const located = url ? null : locator ? locator(path) : `${path}.map`;
|
|
673
|
+
if (located) {
|
|
674
|
+
raw = await readText(located);
|
|
675
|
+
mapDir = dirname(located);
|
|
676
|
+
} else if (!url) {
|
|
677
|
+
return null;
|
|
678
|
+
} else if (url.startsWith("data:")) {
|
|
546
679
|
const comma = url.indexOf(",");
|
|
547
680
|
const encoded = url.slice(comma + 1);
|
|
548
681
|
raw = url.slice(0, comma).endsWith(";base64") ? Buffer.from(encoded, "base64").toString("utf8") : decodeURIComponent(encoded);
|
|
@@ -571,12 +704,13 @@ async function loadMap(path, lines) {
|
|
|
571
704
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
572
705
|
var WITH_FUNCTION = /^at (?:async )?(.+?) \((.+)\)$/;
|
|
573
706
|
var LOCATION = /^(.+):(\d+):(\d+)$/;
|
|
707
|
+
var MAX_FRAME_LINE = 1e3;
|
|
574
708
|
function parseStack(stack) {
|
|
575
709
|
if (!stack) return [];
|
|
576
710
|
const frames = [];
|
|
577
711
|
for (const raw of stack.split("\n")) {
|
|
578
712
|
const line = raw.trim();
|
|
579
|
-
if (!line.startsWith("at ")) continue;
|
|
713
|
+
if (line.length > MAX_FRAME_LINE || !line.startsWith("at ")) continue;
|
|
580
714
|
const withFunction = WITH_FUNCTION.exec(line);
|
|
581
715
|
const fn = withFunction?.[1];
|
|
582
716
|
const location = withFunction ? withFunction[2] ?? "" : line.replace(/^at (?:async )?/, "");
|
|
@@ -671,6 +805,7 @@ var Transport = class {
|
|
|
671
805
|
inFlight = null;
|
|
672
806
|
nextAttemptAt = 0;
|
|
673
807
|
backoffMs = 0;
|
|
808
|
+
unauthorizedUntil = 0;
|
|
674
809
|
stopped = false;
|
|
675
810
|
warned = /* @__PURE__ */ new Set();
|
|
676
811
|
timer;
|
|
@@ -760,10 +895,8 @@ var Transport = class {
|
|
|
760
895
|
this.nextAttemptAt = Date.now() + Math.max(this.backoffMs, outcome.retryAfterMs ?? 0);
|
|
761
896
|
return false;
|
|
762
897
|
case "unauthorized":
|
|
763
|
-
this.droppedCount += events.length
|
|
764
|
-
this.
|
|
765
|
-
this.nextAttemptAt = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
766
|
-
this.warn("unauthorized", `chave do Radar recusada (${outcome.status}); envios pausados por 10 minutos`, true);
|
|
898
|
+
this.droppedCount += events.length;
|
|
899
|
+
this.pauseUnauthorized(outcome.status);
|
|
767
900
|
return false;
|
|
768
901
|
case "rejected":
|
|
769
902
|
this.droppedCount += events.length;
|
|
@@ -771,6 +904,47 @@ var Transport = class {
|
|
|
771
904
|
return true;
|
|
772
905
|
}
|
|
773
906
|
}
|
|
907
|
+
async checkIn(slug, payload) {
|
|
908
|
+
if (this.stopped || !this.config.checkInBaseUrl || Date.now() < this.unauthorizedUntil) return;
|
|
909
|
+
let response;
|
|
910
|
+
try {
|
|
911
|
+
response = await fetch(this.config.checkInBaseUrl + encodeURIComponent(slug), {
|
|
912
|
+
method: "POST",
|
|
913
|
+
headers: this.headers(),
|
|
914
|
+
body: JSON.stringify(payload),
|
|
915
|
+
redirect: "error",
|
|
916
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutMs ?? 5e3)
|
|
917
|
+
});
|
|
918
|
+
} catch (error) {
|
|
919
|
+
this.warn("checkin_network", `check-in no Radar falhou: ${describeError(error)}`, false);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
await response.arrayBuffer().catch(() => void 0);
|
|
923
|
+
if (response.ok) return;
|
|
924
|
+
if (response.status === 401 || response.status === 403) {
|
|
925
|
+
this.pauseUnauthorized(response.status);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (response.status === 404) {
|
|
929
|
+
this.warn(`checkin_unknown_${slug}`, `tarefa agendada "${slug}" n\xE3o existe no projeto da chave; crie o monitor com esse slug no Radar`, true);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
this.warn(`checkin_rejected_${response.status}`, `check-in recusado pelo Radar (${response.status})`, false);
|
|
933
|
+
}
|
|
934
|
+
pauseUnauthorized(status) {
|
|
935
|
+
this.droppedCount += this.queue.length;
|
|
936
|
+
this.queue = [];
|
|
937
|
+
this.unauthorizedUntil = Date.now() + UNAUTHORIZED_PAUSE_MS;
|
|
938
|
+
this.nextAttemptAt = Math.max(this.nextAttemptAt, this.unauthorizedUntil);
|
|
939
|
+
this.warn("unauthorized", `chave do Radar recusada (${status}); envios pausados por 10 minutos`, true);
|
|
940
|
+
}
|
|
941
|
+
headers() {
|
|
942
|
+
return {
|
|
943
|
+
"content-type": "application/json",
|
|
944
|
+
authorization: `Bearer ${this.config.key}`,
|
|
945
|
+
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
946
|
+
};
|
|
947
|
+
}
|
|
774
948
|
requeue(events) {
|
|
775
949
|
this.queue.unshift(...events);
|
|
776
950
|
const overflow = this.queue.length - this.maxQueue;
|
|
@@ -782,11 +956,7 @@ var Transport = class {
|
|
|
782
956
|
async post(batch) {
|
|
783
957
|
const json = JSON.stringify(batch);
|
|
784
958
|
const compress = Buffer.byteLength(json) > GZIP_THRESHOLD_BYTES;
|
|
785
|
-
const headers =
|
|
786
|
-
"content-type": "application/json",
|
|
787
|
-
authorization: `Bearer ${this.config.key}`,
|
|
788
|
-
"user-agent": `${this.config.sdk.name}/${this.config.sdk.version}`
|
|
789
|
-
};
|
|
959
|
+
const headers = this.headers();
|
|
790
960
|
if (compress) headers["content-encoding"] = "gzip";
|
|
791
961
|
let response;
|
|
792
962
|
try {
|
|
@@ -840,9 +1010,12 @@ function withTimeout(promise, ms) {
|
|
|
840
1010
|
|
|
841
1011
|
// src/version.ts
|
|
842
1012
|
var SDK_NAME = "@oconde/radar";
|
|
843
|
-
var SDK_VERSION = true ? "0.
|
|
1013
|
+
var SDK_VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
844
1014
|
|
|
845
1015
|
// src/client.ts
|
|
1016
|
+
var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
|
|
1017
|
+
var MAX_CHECK_IN_ENVIRONMENT = 64;
|
|
1018
|
+
var MAX_CHECK_IN_RELEASE = 128;
|
|
846
1019
|
var RadarClient = class {
|
|
847
1020
|
options = resolveOptions({}, {});
|
|
848
1021
|
transport = null;
|
|
@@ -871,6 +1044,7 @@ var RadarClient = class {
|
|
|
871
1044
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
872
1045
|
environment: this.options.environment,
|
|
873
1046
|
...this.options.release ? { release: this.options.release } : {},
|
|
1047
|
+
checkInBaseUrl: this.options.endpoint + CHECKIN_PATH,
|
|
874
1048
|
onWarning: (message, always) => always ? this.writeWarning(message) : this.debugWarn(message)
|
|
875
1049
|
});
|
|
876
1050
|
}
|
|
@@ -932,6 +1106,51 @@ var RadarClient = class {
|
|
|
932
1106
|
if (currentContext()) return execute();
|
|
933
1107
|
return runWithContext({ requestId: randomUUID2(), startedAt: performance3.now() }, execute);
|
|
934
1108
|
}
|
|
1109
|
+
withContext(fn, options = {}) {
|
|
1110
|
+
return runWithContext({ requestId: requestIdFrom(options.requestId), startedAt: performance3.now() }, fn);
|
|
1111
|
+
}
|
|
1112
|
+
async checkIn(slug, status, options = {}) {
|
|
1113
|
+
try {
|
|
1114
|
+
if (!this.transport || this.closed) return;
|
|
1115
|
+
if (typeof slug !== "string" || !CHECKIN_SLUG.test(slug)) {
|
|
1116
|
+
this.debugWarn(`slug de tarefa agendada inv\xE1lido: ${String(slug).slice(0, 80)}`);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (!CHECKIN_STATUSES.includes(status)) return;
|
|
1120
|
+
const checkInId = typeof options.checkInId === "string" && options.checkInId ? options.checkInId.slice(0, LIMITS.checkInIdLength) : void 0;
|
|
1121
|
+
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;
|
|
1122
|
+
const environment = this.options.environment.slice(0, MAX_CHECK_IN_ENVIRONMENT) || "production";
|
|
1123
|
+
const release = this.options.release?.slice(0, MAX_CHECK_IN_RELEASE);
|
|
1124
|
+
const payload = {
|
|
1125
|
+
status,
|
|
1126
|
+
...checkInId ? { checkInId } : {},
|
|
1127
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1128
|
+
environment,
|
|
1129
|
+
...release ? { release } : {}
|
|
1130
|
+
};
|
|
1131
|
+
await this.transport.checkIn(slug, payload);
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
this.debugWarn(`check-in falhou: ${describeError(error)}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async cron(slug, fn) {
|
|
1137
|
+
const checkInId = randomUUID2();
|
|
1138
|
+
const started = performance3.now();
|
|
1139
|
+
const execute = async () => {
|
|
1140
|
+
await this.checkIn(slug, "in_progress", { checkInId });
|
|
1141
|
+
try {
|
|
1142
|
+
const result = await fn();
|
|
1143
|
+
await this.checkIn(slug, "ok", { checkInId, durationMs: elapsedSince(started) });
|
|
1144
|
+
return result;
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
this.captureError(error, { cron: slug });
|
|
1147
|
+
await this.checkIn(slug, "error", { checkInId, durationMs: elapsedSince(started) });
|
|
1148
|
+
throw error;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
if (currentContext()) return execute();
|
|
1152
|
+
return this.withContext(execute);
|
|
1153
|
+
}
|
|
935
1154
|
middleware() {
|
|
936
1155
|
return createMiddleware(this);
|
|
937
1156
|
}
|
|
@@ -949,7 +1168,7 @@ var RadarClient = class {
|
|
|
949
1168
|
() => this.emitLog(level, "http.request", {
|
|
950
1169
|
method: (req.method ?? "GET").toUpperCase(),
|
|
951
1170
|
...route ? { route } : {},
|
|
952
|
-
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), req
|
|
1171
|
+
...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
|
|
953
1172
|
status,
|
|
954
1173
|
durationMs: elapsedSince(context.startedAt)
|
|
955
1174
|
})
|
|
@@ -986,31 +1205,40 @@ var RadarClient = class {
|
|
|
986
1205
|
this.print(event);
|
|
987
1206
|
this.transport?.enqueue(event);
|
|
988
1207
|
}
|
|
1208
|
+
captureException(exception, options = {}) {
|
|
1209
|
+
this.safely(() => this.emitException(exception, options));
|
|
1210
|
+
}
|
|
989
1211
|
emitError(error, data, level, handled) {
|
|
990
1212
|
if (this.closed) return;
|
|
991
1213
|
if (typeof error === "object" && error !== null) {
|
|
992
1214
|
if (this.captured.has(error)) return;
|
|
993
1215
|
this.captured.add(error);
|
|
994
1216
|
}
|
|
1217
|
+
this.emitException(exceptionInfo(error), { level, handled, ...data !== void 0 ? { attrs: data } : {} }, error instanceof Error ? error.stack : void 0);
|
|
1218
|
+
}
|
|
1219
|
+
emitException(exception, options, stack) {
|
|
1220
|
+
if (this.closed) return;
|
|
995
1221
|
const context = currentContext();
|
|
996
1222
|
const mode = this.options.redact;
|
|
1223
|
+
const request = options.request === null ? void 0 : options.request ?? (context?.req ? requestInfo(context.req, mode, { route: context.route }, this.options.requestDetail) : void 0);
|
|
1224
|
+
const runtime = options.runtime === void 0 ? runtimeInfo() : options.runtime;
|
|
997
1225
|
const event = {
|
|
998
1226
|
type: "error",
|
|
999
1227
|
ts: Date.now(),
|
|
1000
|
-
level,
|
|
1001
|
-
handled,
|
|
1002
|
-
exception
|
|
1228
|
+
level: options.level ?? "error",
|
|
1229
|
+
handled: options.handled ?? true,
|
|
1230
|
+
exception,
|
|
1003
1231
|
...context ? { requestId: context.requestId } : {},
|
|
1004
|
-
...
|
|
1232
|
+
...request ? { request } : {},
|
|
1005
1233
|
...context?.user ? { user: context.user } : {},
|
|
1006
|
-
runtime:
|
|
1007
|
-
...
|
|
1234
|
+
...runtime ? { runtime } : {},
|
|
1235
|
+
...options.attrs !== void 0 ? { attrs: prepareAttrs(options.attrs, mode) } : {}
|
|
1008
1236
|
};
|
|
1009
|
-
this.print(event,
|
|
1237
|
+
this.print(event, stack);
|
|
1010
1238
|
if (!this.transport) return;
|
|
1011
1239
|
this.transport.enqueue(
|
|
1012
|
-
() => enrichException(event.exception).then(
|
|
1013
|
-
(
|
|
1240
|
+
() => enrichException(event.exception, process.cwd(), options.enrich).then(
|
|
1241
|
+
(enriched) => ({ ...event, exception: enriched }),
|
|
1014
1242
|
() => event
|
|
1015
1243
|
)
|
|
1016
1244
|
);
|