@oconde/radar 0.3.0 → 0.4.2

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Correções
6
+
7
+ - SvelteKit: um `redirect()` ou `error()` lançado por um hook depois do `radarHandle` (um portão de login, por exemplo) é registrado com o status que o visitante recebe (303, 404). Antes, o log `http.request` saía como 500, contando como erro no Desempenho e na meta de disponibilidade.
8
+
9
+ ## 0.4.1
10
+
11
+ ### Correções
12
+
13
+ - `logRequests: { sample: 0 }` não registra nenhuma requisição. Antes, 0 era tratado como valor inválido e virava 1, guardando todas.
14
+
15
+ ## 0.4.0
16
+
17
+ ### Novo
18
+
19
+ - `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.
20
+ - `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.
21
+
22
+ ### Correções
23
+
24
+ - Uma RegExp com a flag `g` em `propagateTo` deixava de mandar o `x-request-id` em chamadas alternadas.
25
+
3
26
  ## 0.3.0
4
27
 
5
28
  ### Novo
package/README.md CHANGED
@@ -104,7 +104,20 @@ await radar.cron('nightly-report', () => buildReport());
104
104
  radar.init({ key: process.env.RADAR_KEY, traceFetch: { propagateTo: ['api.spinlab.dev', '.oconde.dev'] } });
105
105
  ```
106
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.
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.
108
121
 
109
122
  ## Erros com o seu código
110
123
 
@@ -38,10 +38,24 @@ type RequestDetail = 'full' | 'route';
38
38
  type FetchTraceTarget = string | RegExp;
39
39
  type TraceFetchOption = boolean | {
40
40
  propagateTo?: FetchTraceTarget[];
41
+ ignore?: FetchTraceTarget[];
41
42
  };
42
43
  type ResolvedTraceFetch = {
43
44
  enabled: boolean;
44
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;
45
59
  };
46
60
  type RadarOptions = {
47
61
  key?: string;
@@ -53,7 +67,7 @@ type RadarOptions = {
53
67
  minLevel?: LogLevel;
54
68
  redact?: RedactMode;
55
69
  requestDetail?: RequestDetail;
56
- logRequests?: boolean;
70
+ logRequests?: LogRequestsOption;
57
71
  ignorePaths?: string[];
58
72
  captureUnhandled?: boolean;
59
73
  traceFetch?: TraceFetchOption;
@@ -69,7 +83,7 @@ type ResolvedOptions = {
69
83
  minLevel: LogLevel;
70
84
  redact: RedactMode;
71
85
  requestDetail: RequestDetail;
72
- logRequests: boolean;
86
+ logRequests: ResolvedLogRequests;
73
87
  ignorePaths: string[];
74
88
  captureUnhandled: boolean;
75
89
  traceFetch: ResolvedTraceFetch;
@@ -38,10 +38,24 @@ type RequestDetail = 'full' | 'route';
38
38
  type FetchTraceTarget = string | RegExp;
39
39
  type TraceFetchOption = boolean | {
40
40
  propagateTo?: FetchTraceTarget[];
41
+ ignore?: FetchTraceTarget[];
41
42
  };
42
43
  type ResolvedTraceFetch = {
43
44
  enabled: boolean;
44
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;
45
59
  };
46
60
  type RadarOptions = {
47
61
  key?: string;
@@ -53,7 +67,7 @@ type RadarOptions = {
53
67
  minLevel?: LogLevel;
54
68
  redact?: RedactMode;
55
69
  requestDetail?: RequestDetail;
56
- logRequests?: boolean;
70
+ logRequests?: LogRequestsOption;
57
71
  ignorePaths?: string[];
58
72
  captureUnhandled?: boolean;
59
73
  traceFetch?: TraceFetchOption;
@@ -69,7 +83,7 @@ type ResolvedOptions = {
69
83
  minLevel: LogLevel;
70
84
  redact: RedactMode;
71
85
  requestDetail: RequestDetail;
72
- logRequests: boolean;
86
+ logRequests: ResolvedLogRequests;
73
87
  ignorePaths: string[];
74
88
  captureUnhandled: boolean;
75
89
  traceFetch: ResolvedTraceFetch;
package/dist/index.cjs CHANGED
@@ -280,6 +280,12 @@ 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;
@@ -419,11 +425,24 @@ function createMiddleware(logger) {
419
425
  // src/options.ts
420
426
  var DEFAULT_ENDPOINT = "https://radar-ingest.oconde.dev";
421
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
+ }
422
436
  function resolveTraceFetch(value) {
423
- if (!value) return { enabled: false, propagateTo: [] };
424
- if (value === true) return { enabled: true, propagateTo: [] };
425
- const targets2 = Array.isArray(value.propagateTo) ? value.propagateTo : [];
426
- return { enabled: true, propagateTo: targets2.filter((target) => typeof target === "string" ? target.trim() !== "" : target instanceof RegExp) };
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 };
427
446
  }
428
447
  function resolveOptions(options = {}, env = process.env) {
429
448
  const key = options.key?.trim() || void 0;
@@ -437,7 +456,7 @@ function resolveOptions(options = {}, env = process.env) {
437
456
  minLevel: options.minLevel && LEVELS.includes(options.minLevel) ? options.minLevel : "info",
438
457
  redact: options.redact === "none" ? "none" : "mask",
439
458
  requestDetail: options.requestDetail === "route" ? "route" : "full",
440
- logRequests: options.logRequests ?? true,
459
+ logRequests: resolveLogRequests(options.logRequests),
441
460
  ignorePaths: options.ignorePaths ?? ["/healthz", "/health"],
442
461
  captureUnhandled: options.captureUnhandled ?? true,
443
462
  traceFetch: resolveTraceFetch(options.traceFetch),
@@ -591,8 +610,8 @@ function headerOf(code, index, prefix) {
591
610
  return header;
592
611
  }
593
612
  function nameIn(header) {
594
- const patterns = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
595
- for (const pattern of patterns) {
613
+ const patterns2 = ANONYMOUS_FUNCTION.test(header) ? NAME_PATTERNS : [...NAME_PATTERNS, METHOD_PATTERN];
614
+ for (const pattern of patterns2) {
596
615
  const name = pattern.exec(header)?.[1];
597
616
  if (name && !KEYWORDS.has(name)) return name.slice(0, MAX_NAME_LENGTH);
598
617
  }
@@ -912,12 +931,19 @@ function originOf(value) {
912
931
  return null;
913
932
  }
914
933
  }
915
- function targets(url, target) {
916
- if (target instanceof RegExp) return target.test(url.host);
917
- const wanted = target.trim().toLowerCase();
934
+ function hostMatches(url, host) {
935
+ const wanted = host.trim().toLowerCase();
918
936
  if (wanted.startsWith(".")) return url.hostname.toLowerCase().endsWith(wanted);
919
937
  return url.host.toLowerCase() === wanted || url.hostname.toLowerCase() === wanted;
920
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
+ }
921
947
  function isPlainObject(value) {
922
948
  if (value === null || typeof value !== "object") return false;
923
949
  const prototype = Object.getPrototypeOf(value);
@@ -941,6 +967,7 @@ function traceFetch(config) {
941
967
  const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
942
968
  const context = currentContext();
943
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);
944
971
  const attrs = { method, host: url.host, path: outboundPath(url.pathname, config.redact) };
945
972
  const started = performance.now();
946
973
  try {
@@ -1189,7 +1216,7 @@ function withTimeout(promise, ms) {
1189
1216
 
1190
1217
  // src/version.ts
1191
1218
  var SDK_NAME = "@oconde/radar";
1192
- var SDK_VERSION = true ? "0.3.0" : "0.0.0-dev";
1219
+ var SDK_VERSION = true ? "0.4.2" : "0.0.0-dev";
1193
1220
 
1194
1221
  // src/client.ts
1195
1222
  var MAX_CHECK_IN_DURATION_MS = 7 * 864e5;
@@ -1233,6 +1260,7 @@ var RadarClient = class {
1233
1260
  this.restoreFetch = traceFetch({
1234
1261
  endpoint: this.options.endpoint,
1235
1262
  propagateTo: this.options.traceFetch.propagateTo,
1263
+ ignore: this.options.traceFetch.ignore,
1236
1264
  redact: this.options.redact,
1237
1265
  record: (level, attrs) => this.safely(() => this.emitLog(level, "http.client", attrs))
1238
1266
  });
@@ -1343,22 +1371,28 @@ var RadarClient = class {
1343
1371
  return createMiddleware(this);
1344
1372
  }
1345
1373
  shouldLogRequest(req) {
1346
- return this.options.logRequests && !this.options.ignorePaths.includes(requestPath(req));
1374
+ return this.options.logRequests.enabled && !this.options.ignorePaths.includes(requestPath(req));
1347
1375
  }
1348
1376
  logHttpRequest(context, status) {
1349
1377
  this.safely(() => {
1350
1378
  const req = context.req;
1351
1379
  if (!req) return;
1352
- const level = status >= 500 ? "error" : status >= 400 ? "warn" : "info";
1353
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";
1354
1387
  runWithContext(
1355
1388
  context,
1356
1389
  () => this.emitLog(level, "http.request", {
1357
1390
  method: (req.method ?? "GET").toUpperCase(),
1358
1391
  ...route ? { route } : {},
1359
- ...this.options.requestDetail === "full" ? { path: redactPath(requestPath(req), pathParams(req, requestPath(req)), this.options.redact) } : {},
1392
+ ...this.options.requestDetail === "full" ? { path: redactPath(path, pathParams(req, path), this.options.redact) } : {},
1360
1393
  status,
1361
- durationMs: elapsedSince(context.startedAt)
1394
+ durationMs: elapsedSince(context.startedAt),
1395
+ ...sample < 1 ? { sampleRate: sample } : {}
1362
1396
  })
1363
1397
  );
1364
1398
  });