@oconde/radar 0.2.0 → 0.4.1
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 +27 -0
- package/README.md +21 -0
- package/dist/{client-CZHtsQAd.d.cts → client-DRZ7htMJ.d.cts} +27 -2
- package/dist/{client-DbBsXgpj.d.ts → client-FkXWx8sJ.d.ts} +27 -2
- package/dist/index.cjs +218 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +227 -30
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +218 -21
- 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 +227 -30
- package/dist/nest/index.js.map +1 -1
- package/dist/sveltekit/index.cjs +220 -23
- package/dist/sveltekit/index.cjs.map +1 -1
- package/dist/sveltekit/index.d.cts +1 -1
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/index.js +231 -34
- package/dist/sveltekit/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
### Correções
|
|
6
|
+
|
|
7
|
+
- `logRequests: { sample: 0 }` não registra nenhuma requisição. Antes, 0 era tratado como valor inválido e virava 1, guardando todas.
|
|
8
|
+
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
### Novo
|
|
12
|
+
|
|
13
|
+
- `logRequests` aceita filtros e amostragem: `{ include, exclude, sample }`. `include` e `exclude` recebem caminhos ou rotas (`'/dashboard'` vale para `/dashboard` e tudo abaixo dele) ou RegExp, testados no caminho e na rota do framework. `sample` (de 0 a 1) guarda só essa fração das requisições, sorteada de forma uniforme, e cada log guardado leva `sampleRate`, que o Radar usa para estimar os totais. `logRequests: true` e `false` continuam valendo.
|
|
14
|
+
- `traceFetch.ignore`: hosts (o nome exato ou `.dominio`) ou RegExp testadas em `host + caminho` que não viram log `http.client`. As chamadas continuam saindo normalmente.
|
|
15
|
+
|
|
16
|
+
### Correções
|
|
17
|
+
|
|
18
|
+
- Uma RegExp com a flag `g` em `propagateTo` deixava de mandar o `x-request-id` em chamadas alternadas.
|
|
19
|
+
|
|
20
|
+
## 0.3.0
|
|
21
|
+
|
|
22
|
+
### Novo
|
|
23
|
+
|
|
24
|
+
- `traceFetch`: cada `fetch` de saída vira um log `http.client` com método, host, caminho (sem a query, com trechos que parecem segredo mascarados), status e duração, ou o erro com a causa (`fetch failed (ECONNREFUSED)`). Para os hosts de `propagateTo`, o SDK manda o `x-request-id` da requisição atual, sem trocar um que o app já tenha posto. Chamadas para o próprio Radar ficam de fora. Desligado por padrão.
|
|
25
|
+
|
|
26
|
+
### Correções
|
|
27
|
+
|
|
28
|
+
- O nome da função inferido do código original não se confunde mais com chaves e com a palavra `function` dentro de comentários de várias linhas (como os de JSDoc).
|
|
29
|
+
|
|
3
30
|
## 0.2.0
|
|
4
31
|
|
|
5
32
|
### Novo
|
package/README.md
CHANGED
|
@@ -98,6 +98,27 @@ await radar.cron('nightly-report', () => buildReport());
|
|
|
98
98
|
|
|
99
99
|
`radar.cron` avisa o Radar no início, no fim e na falha, e o Radar alerta se a tarefa atrasar ou falhar. Em scripts sem o SDK, o check-in é um `POST` (veja o [PROTOCOL.md](PROTOCOL.md)). Para jobs e workers, `radar.withContext(fn)` dá a cada execução o próprio `requestId`. Detalhes em [Tarefas agendadas](https://radar.oconde.dev/docs/sdk/cron/).
|
|
100
100
|
|
|
101
|
+
## Chamadas para outros serviços
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
radar.init({ key: process.env.RADAR_KEY, traceFetch: { propagateTo: ['api.spinlab.dev', '.oconde.dev'] } });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Com `traceFetch`, cada `fetch` que o app faz vira um log `http.client` com método, host, caminho (sem a query, e com trechos que parecem token mascarados), status e duração, ou o erro quando a chamada nem chega a responder. Dentro de uma requisição, o log entra na linha do tempo dela. Para os hosts de `propagateTo` (o nome exato, `.dominio` para os subdomínios ou uma RegExp), o SDK manda o `x-request-id` da requisição atual, e o Radar mostra a mesma requisição nos dois projetos. `traceFetch: true` registra as chamadas sem mandar o id para ninguém. Chamadas para o próprio Radar ficam de fora. Para não registrar chamadas barulhentas, como polling, use `ignore` com hosts ou RegExp testadas em `host + caminho`: `traceFetch: { ignore: ['api.telegram.org', /\/getUpdates$/] }`.
|
|
108
|
+
|
|
109
|
+
## Quais requisições viram log
|
|
110
|
+
|
|
111
|
+
Por padrão, cada requisição vira um log `http.request`, que alimenta o Desempenho e os limites do Radar. Para guardar só uma parte:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
radar.init({
|
|
115
|
+
key: process.env.RADAR_KEY,
|
|
116
|
+
logRequests: { include: ['/dashboard', /^\/api\//], exclude: ['/dashboard/ping'], sample: 0.5 },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`include` e `exclude` recebem caminhos ou rotas (`'/dashboard'` vale para `/dashboard` e tudo abaixo dele) ou RegExp. `sample` guarda só essa fração, sorteada de forma uniforme, e o Radar compensa a amostragem nos totais. `logRequests: false` desliga.
|
|
121
|
+
|
|
101
122
|
## Erros com o seu código
|
|
102
123
|
|
|
103
124
|
Ligue no `tsconfig` para os erros mostrarem as linhas do TypeScript:
|
|
@@ -35,6 +35,28 @@ type RadarMiddleware = (req: RequestLike, res: ResponseLike, next: (error?: unkn
|
|
|
35
35
|
|
|
36
36
|
type RedactMode = 'mask' | 'none';
|
|
37
37
|
type RequestDetail = 'full' | 'route';
|
|
38
|
+
type FetchTraceTarget = string | RegExp;
|
|
39
|
+
type TraceFetchOption = boolean | {
|
|
40
|
+
propagateTo?: FetchTraceTarget[];
|
|
41
|
+
ignore?: FetchTraceTarget[];
|
|
42
|
+
};
|
|
43
|
+
type ResolvedTraceFetch = {
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
propagateTo: FetchTraceTarget[];
|
|
46
|
+
ignore: FetchTraceTarget[];
|
|
47
|
+
};
|
|
48
|
+
type RequestPattern = string | RegExp;
|
|
49
|
+
type LogRequestsOption = boolean | {
|
|
50
|
+
include?: RequestPattern[];
|
|
51
|
+
exclude?: RequestPattern[];
|
|
52
|
+
sample?: number;
|
|
53
|
+
};
|
|
54
|
+
type ResolvedLogRequests = {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
include: RequestPattern[];
|
|
57
|
+
exclude: RequestPattern[];
|
|
58
|
+
sample: number;
|
|
59
|
+
};
|
|
38
60
|
type RadarOptions = {
|
|
39
61
|
key?: string;
|
|
40
62
|
endpoint?: string;
|
|
@@ -45,9 +67,10 @@ type RadarOptions = {
|
|
|
45
67
|
minLevel?: LogLevel;
|
|
46
68
|
redact?: RedactMode;
|
|
47
69
|
requestDetail?: RequestDetail;
|
|
48
|
-
logRequests?:
|
|
70
|
+
logRequests?: LogRequestsOption;
|
|
49
71
|
ignorePaths?: string[];
|
|
50
72
|
captureUnhandled?: boolean;
|
|
73
|
+
traceFetch?: TraceFetchOption;
|
|
51
74
|
debug?: boolean;
|
|
52
75
|
};
|
|
53
76
|
type ResolvedOptions = {
|
|
@@ -60,9 +83,10 @@ type ResolvedOptions = {
|
|
|
60
83
|
minLevel: LogLevel;
|
|
61
84
|
redact: RedactMode;
|
|
62
85
|
requestDetail: RequestDetail;
|
|
63
|
-
logRequests:
|
|
86
|
+
logRequests: ResolvedLogRequests;
|
|
64
87
|
ignorePaths: string[];
|
|
65
88
|
captureUnhandled: boolean;
|
|
89
|
+
traceFetch: ResolvedTraceFetch;
|
|
66
90
|
debug: boolean;
|
|
67
91
|
};
|
|
68
92
|
|
|
@@ -95,6 +119,7 @@ declare class RadarClient {
|
|
|
95
119
|
private initialized;
|
|
96
120
|
private closed;
|
|
97
121
|
private readonly captured;
|
|
122
|
+
private restoreFetch;
|
|
98
123
|
get isEnabled(): boolean;
|
|
99
124
|
get settings(): Readonly<ResolvedOptions>;
|
|
100
125
|
init(options?: RadarOptions): void;
|
|
@@ -35,6 +35,28 @@ type RadarMiddleware = (req: RequestLike, res: ResponseLike, next: (error?: unkn
|
|
|
35
35
|
|
|
36
36
|
type RedactMode = 'mask' | 'none';
|
|
37
37
|
type RequestDetail = 'full' | 'route';
|
|
38
|
+
type FetchTraceTarget = string | RegExp;
|
|
39
|
+
type TraceFetchOption = boolean | {
|
|
40
|
+
propagateTo?: FetchTraceTarget[];
|
|
41
|
+
ignore?: FetchTraceTarget[];
|
|
42
|
+
};
|
|
43
|
+
type ResolvedTraceFetch = {
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
propagateTo: FetchTraceTarget[];
|
|
46
|
+
ignore: FetchTraceTarget[];
|
|
47
|
+
};
|
|
48
|
+
type RequestPattern = string | RegExp;
|
|
49
|
+
type LogRequestsOption = boolean | {
|
|
50
|
+
include?: RequestPattern[];
|
|
51
|
+
exclude?: RequestPattern[];
|
|
52
|
+
sample?: number;
|
|
53
|
+
};
|
|
54
|
+
type ResolvedLogRequests = {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
include: RequestPattern[];
|
|
57
|
+
exclude: RequestPattern[];
|
|
58
|
+
sample: number;
|
|
59
|
+
};
|
|
38
60
|
type RadarOptions = {
|
|
39
61
|
key?: string;
|
|
40
62
|
endpoint?: string;
|
|
@@ -45,9 +67,10 @@ type RadarOptions = {
|
|
|
45
67
|
minLevel?: LogLevel;
|
|
46
68
|
redact?: RedactMode;
|
|
47
69
|
requestDetail?: RequestDetail;
|
|
48
|
-
logRequests?:
|
|
70
|
+
logRequests?: LogRequestsOption;
|
|
49
71
|
ignorePaths?: string[];
|
|
50
72
|
captureUnhandled?: boolean;
|
|
73
|
+
traceFetch?: TraceFetchOption;
|
|
51
74
|
debug?: boolean;
|
|
52
75
|
};
|
|
53
76
|
type ResolvedOptions = {
|
|
@@ -60,9 +83,10 @@ type ResolvedOptions = {
|
|
|
60
83
|
minLevel: LogLevel;
|
|
61
84
|
redact: RedactMode;
|
|
62
85
|
requestDetail: RequestDetail;
|
|
63
|
-
logRequests:
|
|
86
|
+
logRequests: ResolvedLogRequests;
|
|
64
87
|
ignorePaths: string[];
|
|
65
88
|
captureUnhandled: boolean;
|
|
89
|
+
traceFetch: ResolvedTraceFetch;
|
|
66
90
|
debug: boolean;
|
|
67
91
|
};
|
|
68
92
|
|
|
@@ -95,6 +119,7 @@ declare class RadarClient {
|
|
|
95
119
|
private initialized;
|
|
96
120
|
private closed;
|
|
97
121
|
private readonly captured;
|
|
122
|
+
private restoreFetch;
|
|
98
123
|
get isEnabled(): boolean;
|
|
99
124
|
get settings(): Readonly<ResolvedOptions>;
|
|
100
125
|
init(options?: RadarOptions): void;
|
package/dist/index.cjs
CHANGED
|
@@ -280,9 +280,18 @@ function requestPath(req) {
|
|
|
280
280
|
const end = url.indexOf("?");
|
|
281
281
|
return end >= 0 ? url.slice(0, end) : url;
|
|
282
282
|
}
|
|
283
|
+
function matchesRequest(pattern, path, route) {
|
|
284
|
+
const candidates = route === void 0 ? [path] : [path, route];
|
|
285
|
+
if (pattern instanceof RegExp) return candidates.some((value) => value.search(pattern) !== -1);
|
|
286
|
+
const prefix = pattern.endsWith("/") ? pattern : `${pattern}/`;
|
|
287
|
+
return candidates.some((value) => value === pattern || value.startsWith(prefix));
|
|
288
|
+
}
|
|
283
289
|
function requestRoute(req, route) {
|
|
284
290
|
if (route) return route;
|
|
285
|
-
|
|
291
|
+
if (typeof req.route?.path !== "string") return void 0;
|
|
292
|
+
const base = typeof req.baseUrl === "string" ? req.baseUrl.replace(/\/+$/, "") : "";
|
|
293
|
+
if (!base) return req.route.path;
|
|
294
|
+
return req.route.path === "/" ? base : `${base}${req.route.path}`;
|
|
286
295
|
}
|
|
287
296
|
var ROUTE_PARAM = /^:([A-Za-z_$][\w$]*)/;
|
|
288
297
|
function pathSegments(value) {
|
|
@@ -416,6 +425,25 @@ function createMiddleware(logger) {
|
|
|
416
425
|
// src/options.ts
|
|
417
426
|
var DEFAULT_ENDPOINT = "https://radar-ingest.oconde.dev";
|
|
418
427
|
var LEVELS = ["debug", "info", "warn", "error"];
|
|
428
|
+
function patterns(value) {
|
|
429
|
+
if (!Array.isArray(value)) return [];
|
|
430
|
+
return value.flatMap((item) => {
|
|
431
|
+
if (item instanceof RegExp) return [item];
|
|
432
|
+
if (typeof item === "string" && item.trim() !== "") return [item.trim()];
|
|
433
|
+
return [];
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function resolveTraceFetch(value) {
|
|
437
|
+
if (!value) return { enabled: false, propagateTo: [], ignore: [] };
|
|
438
|
+
if (value === true) return { enabled: true, propagateTo: [], ignore: [] };
|
|
439
|
+
return { enabled: true, propagateTo: patterns(value.propagateTo), ignore: patterns(value.ignore) };
|
|
440
|
+
}
|
|
441
|
+
function resolveLogRequests(value) {
|
|
442
|
+
if (value === false) return { enabled: false, include: [], exclude: [], sample: 1 };
|
|
443
|
+
if (value === void 0 || value === true || typeof value !== "object" || value === null) return { enabled: true, include: [], exclude: [], sample: 1 };
|
|
444
|
+
const sample = typeof value.sample === "number" && Number.isFinite(value.sample) && value.sample >= 0 && value.sample <= 1 ? value.sample : 1;
|
|
445
|
+
return { enabled: true, include: patterns(value.include), exclude: patterns(value.exclude), sample };
|
|
446
|
+
}
|
|
419
447
|
function resolveOptions(options = {}, env = process.env) {
|
|
420
448
|
const key = options.key?.trim() || void 0;
|
|
421
449
|
return {
|
|
@@ -428,9 +456,10 @@ function resolveOptions(options = {}, env = process.env) {
|
|
|
428
456
|
minLevel: options.minLevel && LEVELS.includes(options.minLevel) ? options.minLevel : "info",
|
|
429
457
|
redact: options.redact === "none" ? "none" : "mask",
|
|
430
458
|
requestDetail: options.requestDetail === "route" ? "route" : "full",
|
|
431
|
-
logRequests: options.logRequests
|
|
459
|
+
logRequests: resolveLogRequests(options.logRequests),
|
|
432
460
|
ignorePaths: options.ignorePaths ?? ["/healthz", "/health"],
|
|
433
461
|
captureUnhandled: options.captureUnhandled ?? true,
|
|
462
|
+
traceFetch: resolveTraceFetch(options.traceFetch),
|
|
434
463
|
debug: options.debug ?? false
|
|
435
464
|
};
|
|
436
465
|
}
|
|
@@ -498,6 +527,7 @@ var IDENTIFIER = "[A-Za-z_$][\\w$]*";
|
|
|
498
527
|
var MAX_LINES_UP = 400;
|
|
499
528
|
var MAX_HEADER_LINES = 6;
|
|
500
529
|
var MAX_NAME_LENGTH = 100;
|
|
530
|
+
var MAX_COMMENT_LINES = 40;
|
|
501
531
|
var CONTROL_BLOCK = /^\s*(?:\}\s*)?(?:if|else|for|while|do|switch|try|catch|finally|with)\b/;
|
|
502
532
|
var CLASS_BODY = new RegExp(`\\bclass\\s+${IDENTIFIER}`);
|
|
503
533
|
var MARKUP_BOUNDARY = /<\/?(?:script|style|template)\b/i;
|
|
@@ -510,8 +540,58 @@ var NAME_PATTERNS = [
|
|
|
510
540
|
];
|
|
511
541
|
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
542
|
var ANONYMOUS_FUNCTION = /\bfunction\b|=>/;
|
|
513
|
-
|
|
514
|
-
|
|
543
|
+
var STRING_LITERAL = /(["'`])(?:\\.|(?!\1)[^\\])*\1/g;
|
|
544
|
+
function lineCommentAt(text, from) {
|
|
545
|
+
for (let index = text.indexOf("//", from); index >= 0; index = text.indexOf("//", index + 1)) {
|
|
546
|
+
if (index === 0 || text[index - 1] !== ":") return index;
|
|
547
|
+
}
|
|
548
|
+
return -1;
|
|
549
|
+
}
|
|
550
|
+
function blockCommentAt(text, from) {
|
|
551
|
+
for (let index = text.indexOf("/*", from); index >= 0; index = text.indexOf("/*", index + 1)) {
|
|
552
|
+
if (index === 0 || text[index - 1] !== "\\") return index;
|
|
553
|
+
}
|
|
554
|
+
return -1;
|
|
555
|
+
}
|
|
556
|
+
function stripComments(text, insideComment, closesLater) {
|
|
557
|
+
let code = "";
|
|
558
|
+
let position = 0;
|
|
559
|
+
let inside = insideComment;
|
|
560
|
+
while (position < text.length) {
|
|
561
|
+
if (inside) {
|
|
562
|
+
const end = text.indexOf("*/", position);
|
|
563
|
+
if (end < 0) return { code, insideComment: true };
|
|
564
|
+
inside = false;
|
|
565
|
+
position = end + 2;
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const block = blockCommentAt(text, position);
|
|
569
|
+
const line = lineCommentAt(text, position);
|
|
570
|
+
if (line >= 0 && (block < 0 || line < block)) return { code: code + text.slice(position, line), insideComment: false };
|
|
571
|
+
if (block < 0) return { code: code + text.slice(position), insideComment: false };
|
|
572
|
+
if (text.indexOf("*/", block + 2) < 0 && !closesLater()) {
|
|
573
|
+
code += text.slice(position, block + 2);
|
|
574
|
+
position = block + 2;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
code += text.slice(position, block);
|
|
578
|
+
inside = true;
|
|
579
|
+
position = block + 2;
|
|
580
|
+
}
|
|
581
|
+
return { code, insideComment: inside };
|
|
582
|
+
}
|
|
583
|
+
function codeLines(lines, from, start, column) {
|
|
584
|
+
const plain = lines.slice(from, start + 1).map((line) => line.replace(STRING_LITERAL, '""'));
|
|
585
|
+
const closesWithin = (index) => plain.slice(index + 1, index + 1 + MAX_COMMENT_LINES).some((line) => line.includes("*/"));
|
|
586
|
+
const result = [];
|
|
587
|
+
let insideComment = false;
|
|
588
|
+
for (let index = 0; index < plain.length; index++) {
|
|
589
|
+
const text = index === plain.length - 1 && column ? (lines[start] ?? "").slice(0, Math.max(0, column - 1)).replace(STRING_LITERAL, '""') : plain[index] ?? "";
|
|
590
|
+
const stripped = stripComments(text, insideComment, () => closesWithin(index));
|
|
591
|
+
insideComment = stripped.insideComment;
|
|
592
|
+
result.push(stripped.code);
|
|
593
|
+
}
|
|
594
|
+
return result;
|
|
515
595
|
}
|
|
516
596
|
function parenBalance(text) {
|
|
517
597
|
let balance = 0;
|
|
@@ -521,17 +601,17 @@ function parenBalance(text) {
|
|
|
521
601
|
}
|
|
522
602
|
return balance;
|
|
523
603
|
}
|
|
524
|
-
function headerOf(
|
|
604
|
+
function headerOf(code, index, prefix) {
|
|
525
605
|
let header = prefix;
|
|
526
606
|
for (let previous = index - 1; previous >= 0 && previous >= index - MAX_HEADER_LINES; previous--) {
|
|
527
607
|
if (header.trim() !== "" && parenBalance(header) >= 0) break;
|
|
528
|
-
header = `${
|
|
608
|
+
header = `${code[previous] ?? ""} ${header}`;
|
|
529
609
|
}
|
|
530
610
|
return header;
|
|
531
611
|
}
|
|
532
612
|
function nameIn(header) {
|
|
533
|
-
const
|
|
534
|
-
for (const pattern of
|
|
613
|
+
const patterns2 = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
|
|
614
|
+
for (const pattern of patterns2) {
|
|
535
615
|
const name = pattern.exec(header)?.[1];
|
|
536
616
|
if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
|
|
537
617
|
}
|
|
@@ -540,11 +620,12 @@ function nameIn(header) {
|
|
|
540
620
|
function enclosingFunctionName(lines, line, column) {
|
|
541
621
|
const start = line - 1;
|
|
542
622
|
if (start < 0 || start >= lines.length) return void 0;
|
|
623
|
+
const from = Math.max(0, start - MAX_LINES_UP - MAX_HEADER_LINES);
|
|
624
|
+
const cleaned = codeLines(lines, from, start, column);
|
|
543
625
|
let depth = 0;
|
|
544
626
|
for (let index = start; index >= 0 && index >= start - MAX_LINES_UP; index--) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const code = codeOnly(index === start && column ? raw.slice(0, Math.max(0, column - 1)) : raw);
|
|
627
|
+
if (index < start && MARKUP_BOUNDARY.test(lines[index] ?? "")) return void 0;
|
|
628
|
+
const code = cleaned[index - from] ?? "";
|
|
548
629
|
for (let position = code.length - 1; position >= 0; position--) {
|
|
549
630
|
const char = code[position];
|
|
550
631
|
if (char === "}") {
|
|
@@ -554,7 +635,7 @@ function enclosingFunctionName(lines, line, column) {
|
|
|
554
635
|
depth -= 1;
|
|
555
636
|
continue;
|
|
556
637
|
}
|
|
557
|
-
const header = headerOf(
|
|
638
|
+
const header = headerOf(cleaned, index - from, code.slice(0, position));
|
|
558
639
|
if (CLASS_BODY.test(header)) return void 0;
|
|
559
640
|
if (CONTROL_BLOCK.test(header)) continue;
|
|
560
641
|
const name = nameIn(header);
|
|
@@ -796,10 +877,6 @@ function safeStringify(value) {
|
|
|
796
877
|
}
|
|
797
878
|
}
|
|
798
879
|
|
|
799
|
-
// src/transport.ts
|
|
800
|
-
var import_node_util2 = require("util");
|
|
801
|
-
var import_node_zlib = require("zlib");
|
|
802
|
-
|
|
803
880
|
// src/util.ts
|
|
804
881
|
var import_node_perf_hooks2 = require("perf_hooks");
|
|
805
882
|
function describeError(error) {
|
|
@@ -809,7 +886,109 @@ function elapsedSince(startedAt) {
|
|
|
809
886
|
return Math.round(import_node_perf_hooks2.performance.now() - startedAt);
|
|
810
887
|
}
|
|
811
888
|
|
|
889
|
+
// src/trace-fetch.ts
|
|
890
|
+
var WRAPPED = /* @__PURE__ */ Symbol.for("@oconde/radar.fetch");
|
|
891
|
+
var REQUEST_ID_HEADER = "x-request-id";
|
|
892
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
893
|
+
var TELEGRAM_BOT = /^bot\d{5,}:[\w-]{20,}$/;
|
|
894
|
+
var TOKEN_LIKE = /^[\w\-.~+=]{20,}$/;
|
|
895
|
+
function secretSegment(segment) {
|
|
896
|
+
if (UUID.test(segment)) return false;
|
|
897
|
+
if (TELEGRAM_BOT.test(segment) || looksLikeCredential(segment)) return true;
|
|
898
|
+
return TOKEN_LIKE.test(segment) && /[A-Za-z]/.test(segment) && /\d/.test(segment);
|
|
899
|
+
}
|
|
900
|
+
function outboundPath(pathname, mode) {
|
|
901
|
+
if (mode === "none") return pathname;
|
|
902
|
+
return pathname.split("/").map((segment) => {
|
|
903
|
+
let decoded = segment;
|
|
904
|
+
try {
|
|
905
|
+
decoded = decodeURIComponent(segment);
|
|
906
|
+
} catch {
|
|
907
|
+
decoded = segment;
|
|
908
|
+
}
|
|
909
|
+
return secretSegment(decoded) ? maskValue(decoded, false) : segment;
|
|
910
|
+
}).join("/");
|
|
911
|
+
}
|
|
912
|
+
function failureOf(error) {
|
|
913
|
+
const message = describeError(error);
|
|
914
|
+
const cause = error instanceof Error ? error.cause : void 0;
|
|
915
|
+
const detail = typeof cause?.code === "string" ? cause.code : typeof cause?.message === "string" ? cause.message : "";
|
|
916
|
+
return detail && !message.includes(detail) ? `${message} (${detail})` : message;
|
|
917
|
+
}
|
|
918
|
+
function urlOf(input) {
|
|
919
|
+
try {
|
|
920
|
+
if (typeof input === "string") return new URL(input);
|
|
921
|
+
if (input instanceof URL) return input;
|
|
922
|
+
return new URL(input.url);
|
|
923
|
+
} catch {
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function originOf(value) {
|
|
928
|
+
try {
|
|
929
|
+
return new URL(value).origin;
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function hostMatches(url, host) {
|
|
935
|
+
const wanted = host.trim().toLowerCase();
|
|
936
|
+
if (wanted.startsWith(".")) return url.hostname.toLowerCase().endsWith(wanted);
|
|
937
|
+
return url.host.toLowerCase() === wanted || url.hostname.toLowerCase() === wanted;
|
|
938
|
+
}
|
|
939
|
+
function targets(url, target) {
|
|
940
|
+
if (target instanceof RegExp) return url.host.search(target) !== -1;
|
|
941
|
+
return hostMatches(url, target);
|
|
942
|
+
}
|
|
943
|
+
function ignored(url, target) {
|
|
944
|
+
if (target instanceof RegExp) return `${url.host}${url.pathname}`.search(target) !== -1;
|
|
945
|
+
return hostMatches(url, target);
|
|
946
|
+
}
|
|
947
|
+
function isPlainObject(value) {
|
|
948
|
+
if (value === null || typeof value !== "object") return false;
|
|
949
|
+
const prototype = Object.getPrototypeOf(value);
|
|
950
|
+
return prototype === Object.prototype || prototype === null;
|
|
951
|
+
}
|
|
952
|
+
function withRequestId(input, init, requestId) {
|
|
953
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
954
|
+
if (headers.has(REQUEST_ID_HEADER)) return [input, init];
|
|
955
|
+
headers.set(REQUEST_ID_HEADER, requestId);
|
|
956
|
+
if (init === void 0 || isPlainObject(init)) return [input, { ...init, headers }];
|
|
957
|
+
return [new Request(input, init), { headers }];
|
|
958
|
+
}
|
|
959
|
+
function traceFetch(config) {
|
|
960
|
+
const current = globalThis.fetch;
|
|
961
|
+
if (typeof current !== "function" || current[WRAPPED]) return () => void 0;
|
|
962
|
+
const original = current;
|
|
963
|
+
const radarOrigin = originOf(config.endpoint);
|
|
964
|
+
const traced = async (input, init) => {
|
|
965
|
+
const url = urlOf(input);
|
|
966
|
+
if (!url || url.origin === radarOrigin || url.protocol !== "http:" && url.protocol !== "https:") return original(input, init);
|
|
967
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
968
|
+
const context = currentContext();
|
|
969
|
+
const [nextInput, nextInit] = context && config.propagateTo.some((target) => targets(url, target)) ? withRequestId(input, init, context.requestId) : [input, init];
|
|
970
|
+
if (config.ignore?.some((target) => ignored(url, target))) return original(nextInput, nextInit);
|
|
971
|
+
const attrs = { method, host: url.host, path: outboundPath(url.pathname, config.redact) };
|
|
972
|
+
const started = performance.now();
|
|
973
|
+
try {
|
|
974
|
+
const response = await original(nextInput, nextInit);
|
|
975
|
+
config.record(response.status >= 500 ? "error" : response.status >= 400 ? "warn" : "info", { ...attrs, status: response.status, durationMs: elapsedSince(started) });
|
|
976
|
+
return response;
|
|
977
|
+
} catch (error) {
|
|
978
|
+
config.record("error", { ...attrs, error: failureOf(error), durationMs: elapsedSince(started) });
|
|
979
|
+
throw error;
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
Object.defineProperty(traced, WRAPPED, { value: original });
|
|
983
|
+
globalThis.fetch = traced;
|
|
984
|
+
return () => {
|
|
985
|
+
if (globalThis.fetch === traced) globalThis.fetch = original;
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
812
989
|
// src/transport.ts
|
|
990
|
+
var import_node_util2 = require("util");
|
|
991
|
+
var import_node_zlib = require("zlib");
|
|
813
992
|
var gzipAsync = (0, import_node_util2.promisify)(import_node_zlib.gzip);
|
|
814
993
|
var MAX_BATCH_BYTES = 9e5;
|
|
815
994
|
var GZIP_THRESHOLD_BYTES = 8192;
|
|
@@ -1037,7 +1216,7 @@ function withTimeout(promise, ms) {
|
|
|
1037
1216
|
|
|
1038
1217
|
// src/version.ts
|
|
1039
1218
|
var SDK_NAME = "@oconde/radar";
|
|
1040
|
-
var SDK_VERSION = true ? "0.
|
|
1219
|
+
var SDK_VERSION = true ? "0.4.1" : "0.0.0-dev";
|
|
1041
1220
|
|
|
1042
1221
|
// src/client.ts
|
|
1043
1222
|
var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
|
|
@@ -1049,6 +1228,7 @@ var RadarClient = class {
|
|
|
1049
1228
|
initialized = false;
|
|
1050
1229
|
closed = false;
|
|
1051
1230
|
captured = /* @__PURE__ */ new WeakSet();
|
|
1231
|
+
restoreFetch = null;
|
|
1052
1232
|
get isEnabled() {
|
|
1053
1233
|
return this.transport !== null && !this.closed;
|
|
1054
1234
|
}
|
|
@@ -1076,6 +1256,15 @@ var RadarClient = class {
|
|
|
1076
1256
|
});
|
|
1077
1257
|
}
|
|
1078
1258
|
if (this.options.captureUnhandled && (this.transport || this.options.console)) installProcessHandlers(this);
|
|
1259
|
+
if (this.options.traceFetch.enabled && (this.transport || this.options.console)) {
|
|
1260
|
+
this.restoreFetch = traceFetch({
|
|
1261
|
+
endpoint: this.options.endpoint,
|
|
1262
|
+
propagateTo: this.options.traceFetch.propagateTo,
|
|
1263
|
+
ignore: this.options.traceFetch.ignore,
|
|
1264
|
+
redact: this.options.redact,
|
|
1265
|
+
record: (level, attrs) => this.safely(() => this.emitLog(level, "http.client", attrs))
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1079
1268
|
} catch (error) {
|
|
1080
1269
|
this.debugWarn(`radar.init falhou: ${describeError(error)}`);
|
|
1081
1270
|
}
|
|
@@ -1182,22 +1371,28 @@ var RadarClient = class {
|
|
|
1182
1371
|
return createMiddleware(this);
|
|
1183
1372
|
}
|
|
1184
1373
|
shouldLogRequest(req) {
|
|
1185
|
-
return this.options.logRequests && !this.options.ignorePaths.includes(requestPath(req));
|
|
1374
|
+
return this.options.logRequests.enabled && !this.options.ignorePaths.includes(requestPath(req));
|
|
1186
1375
|
}
|
|
1187
1376
|
logHttpRequest(context, status) {
|
|
1188
1377
|
this.safely(() => {
|
|
1189
1378
|
const req = context.req;
|
|
1190
1379
|
if (!req) return;
|
|
1191
|
-
const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
|
|
1192
1380
|
const route = requestRoute(req, context.route);
|
|
1381
|
+
const path = requestPath(req);
|
|
1382
|
+
const { include, exclude, sample } = this.options.logRequests;
|
|
1383
|
+
if (include.length > 0 && !include.some((pattern) => matchesRequest(pattern, path, route))) return;
|
|
1384
|
+
if (exclude.some((pattern) => matchesRequest(pattern, path, route))) return;
|
|
1385
|
+
if (sample < 1 && Math.random() >= sample) return;
|
|
1386
|
+
const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
|
|
1193
1387
|
runWithContext(
|
|
1194
1388
|
context,
|
|
1195
1389
|
() => this.emitLog(level, "http.request", {
|
|
1196
1390
|
method: (req.method ?? "GET").toUpperCase(),
|
|
1197
1391
|
...route ? { route } : {},
|
|
1198
|
-
...this.options.requestDetail === "full" ? { path: redactPath(
|
|
1392
|
+
...this.options.requestDetail === "full" ? { path: redactPath(path, pathParams(req, path), this.options.redact) } : {},
|
|
1199
1393
|
status,
|
|
1200
|
-
durationMs: elapsedSince(context.startedAt)
|
|
1394
|
+
durationMs: elapsedSince(context.startedAt),
|
|
1395
|
+
...sample < 1 ? { sampleRate: sample } : {}
|
|
1201
1396
|
})
|
|
1202
1397
|
);
|
|
1203
1398
|
});
|
|
@@ -1212,6 +1407,8 @@ var RadarClient = class {
|
|
|
1212
1407
|
async close(timeoutMs = 2e3) {
|
|
1213
1408
|
if (this.closed) return;
|
|
1214
1409
|
await this.flush(timeoutMs);
|
|
1410
|
+
this.restoreFetch?.();
|
|
1411
|
+
this.restoreFetch = null;
|
|
1215
1412
|
this.closed = true;
|
|
1216
1413
|
this.transport?.stop();
|
|
1217
1414
|
}
|