fg-tracker-sdk 0.4.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +375 -0
  3. package/dist/cli/release.d.ts +3 -0
  4. package/dist/cli/release.d.ts.map +1 -0
  5. package/dist/cli/release.js +134 -0
  6. package/dist/cli/release.js.map +1 -0
  7. package/dist/cli/sourcemaps.d.ts +3 -0
  8. package/dist/cli/sourcemaps.d.ts.map +1 -0
  9. package/dist/cli/sourcemaps.js +98 -0
  10. package/dist/cli/sourcemaps.js.map +1 -0
  11. package/dist/constants.d.ts +28 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +35 -0
  14. package/dist/constants.js.map +1 -0
  15. package/dist/context.d.ts +22 -0
  16. package/dist/context.d.ts.map +1 -0
  17. package/dist/context.js +42 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/database.d.ts +29 -0
  20. package/dist/database.d.ts.map +1 -0
  21. package/dist/database.js +157 -0
  22. package/dist/database.js.map +1 -0
  23. package/dist/express.d.ts +17 -0
  24. package/dist/express.d.ts.map +1 -0
  25. package/dist/express.js +125 -0
  26. package/dist/express.js.map +1 -0
  27. package/dist/fetch.d.ts +9 -0
  28. package/dist/fetch.d.ts.map +1 -0
  29. package/dist/fetch.js +78 -0
  30. package/dist/fetch.js.map +1 -0
  31. package/dist/global-errors.d.ts +11 -0
  32. package/dist/global-errors.d.ts.map +1 -0
  33. package/dist/global-errors.js +35 -0
  34. package/dist/global-errors.js.map +1 -0
  35. package/dist/index.d.ts +29 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +154 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/logs/nest-logger.d.ts +40 -0
  40. package/dist/logs/nest-logger.d.ts.map +1 -0
  41. package/dist/logs/nest-logger.js +99 -0
  42. package/dist/logs/nest-logger.js.map +1 -0
  43. package/dist/logs/pino.d.ts +24 -0
  44. package/dist/logs/pino.d.ts.map +1 -0
  45. package/dist/logs/pino.js +62 -0
  46. package/dist/logs/pino.js.map +1 -0
  47. package/dist/logs/shipper.d.ts +18 -0
  48. package/dist/logs/shipper.d.ts.map +1 -0
  49. package/dist/logs/shipper.js +217 -0
  50. package/dist/logs/shipper.js.map +1 -0
  51. package/dist/logs/types.d.ts +84 -0
  52. package/dist/logs/types.d.ts.map +1 -0
  53. package/dist/logs/types.js +47 -0
  54. package/dist/logs/types.js.map +1 -0
  55. package/dist/logs/winston.d.ts +31 -0
  56. package/dist/logs/winston.d.ts.map +1 -0
  57. package/dist/logs/winston.js +57 -0
  58. package/dist/logs/winston.js.map +1 -0
  59. package/dist/monitors.d.ts +8 -0
  60. package/dist/monitors.d.ts.map +1 -0
  61. package/dist/monitors.js +69 -0
  62. package/dist/monitors.js.map +1 -0
  63. package/dist/next.d.ts +9 -0
  64. package/dist/next.d.ts.map +1 -0
  65. package/dist/next.js +55 -0
  66. package/dist/next.js.map +1 -0
  67. package/dist/normalize.d.ts +12 -0
  68. package/dist/normalize.d.ts.map +1 -0
  69. package/dist/normalize.js +113 -0
  70. package/dist/normalize.js.map +1 -0
  71. package/dist/pg.d.ts +9 -0
  72. package/dist/pg.d.ts.map +1 -0
  73. package/dist/pg.js +83 -0
  74. package/dist/pg.js.map +1 -0
  75. package/dist/releases.d.ts +5 -0
  76. package/dist/releases.d.ts.map +1 -0
  77. package/dist/releases.js +30 -0
  78. package/dist/releases.js.map +1 -0
  79. package/dist/scrub.d.ts +11 -0
  80. package/dist/scrub.d.ts.map +1 -0
  81. package/dist/scrub.js +34 -0
  82. package/dist/scrub.js.map +1 -0
  83. package/dist/tracker.d.ts +99 -0
  84. package/dist/tracker.d.ts.map +1 -0
  85. package/dist/tracker.js +400 -0
  86. package/dist/tracker.js.map +1 -0
  87. package/dist/types.d.ts +264 -0
  88. package/dist/types.d.ts.map +1 -0
  89. package/dist/types.js +2 -0
  90. package/dist/types.js.map +1 -0
  91. package/package.json +98 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog — fg-tracker-sdk
2
+
3
+ ## 0.4.0 — 2026-09-15
4
+
5
+ - **Renomeado de `stread-track-sdk` — nomes antigos continuam funcionando e serão removidos na 1.0.** Veja "Migração de stread-track-sdk" no README.
6
+ - Bins `fg-tracker-release` e `fg-tracker-sourcemaps`; `stread-track-release` e `stread-track-sourcemaps` continuam como aliases obsoletos (avisam no stderr).
7
+ - Variáveis `FG_TRACKER_KEY`, `FG_TRACKER_URL`, `FG_TRACKER_RELEASE`, `FG_TRACKER_ENV` e `FG_TRACKER_SERVICE`, lidas antes dos nomes legados `STREAD_TRACK_*` (que seguem como fallback).
8
+ - Header da chave passa a ser `x-fg-tracker-key` (a API aceita também o legado `x-stread-track-key`). Requer a API do FG Tracker com suporte ao header novo.
9
+ - `FgTrackerTransport` (winston) e o tipo `FgTrackerNestLogger`; `StreadTrackTransport` e `StreadTrackNestLogger` ficam como aliases `@deprecated`.
10
+ - Novos exports: `API_KEY_HEADER`, `DEFAULT_ENDPOINT` (continua `http://localhost:4050`) e `readEnv()`.
11
+ - `instrumentPg` reconhece pools já instrumentados pelo `stread-track-sdk` (não duplica spans).
12
+ - Prefixo dos avisos no console: `[fg-tracker]`.
13
+ - `package.json`: repositório `FORTGROUND/fg-tracker`, `publishConfig.access: public`, `prepack` limpa e recompila `dist/`.
14
+
15
+ ## 0.3.0
16
+
17
+ - Última versão publicada como `stread-track-sdk`.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # fg-tracker-sdk
2
+
3
+ Instrumentação Node para o [FG Tracker](../../README.md) e envio de logs (pino, winston, NestJS Logger). Única dependência de runtime: `pino-abstract-transport`; usa `AsyncLocalStorage` para encadear spans e o `fetch` global do Node para enviar lotes ao `POST /v1/ingest`.
4
+
5
+ Requisitos: Node ≥ 20 (recomendado 24). O pacote é ESM; em projetos CommonJS (NestJS) funciona com `require()` no Node ≥ 22.12 ou via `import()` dinâmico.
6
+
7
+ ## Instalação
8
+
9
+ ```bash
10
+ pnpm add fg-tracker-sdk # publicado no npm
11
+ # ou, dentro do workspace:
12
+ # "fg-tracker-sdk": "workspace:*"
13
+ ```
14
+
15
+ ## Endpoint
16
+
17
+ O default de `endpoint` é `http://localhost:4050` (constante `DEFAULT_ENDPOINT`, API local do monorepo). Em produção passe a URL da API mostrada no painel em **Conectar** (por exemplo via `FG_TRACKER_URL`). O domínio `https://tracker-api.fortground.com.br` será o endereço definitivo da API; até ele entrar no ar, use a URL atual do painel.
18
+
19
+ ## API
20
+
21
+ ```ts
22
+ import { createTracker } from 'fg-tracker-sdk';
23
+
24
+ const tracker = createTracker({
25
+ apiKey: process.env.FG_TRACKER_KEY!, // stk_...
26
+ endpoint: 'http://localhost:4050', // default
27
+ serviceName: 'minha-app',
28
+ flushIntervalMs: 2000, // default
29
+ });
30
+
31
+ app.use(tracker.express()); // Express / NestJS (platform-express)
32
+ tracker.instrumentPg(pool); // pg.Pool → spans db.query com query normalizada
33
+ await tracker.span('custom', 'send-email', async (span) => { span.setAttribute('to', email); });
34
+ export const GET = tracker.wrapNextRoute(async (req) => Response.json({ ok: true }));
35
+ const fetchTracked = tracker.wrapFetch(); // fetch instrumentado → spans http.client
36
+ await tracker.flush(); await tracker.shutdown();
37
+ ```
38
+
39
+ | opção | default | descrição |
40
+ |---|---|---|
41
+ | `apiKey` | — | chave do projeto; sem ela o tracker vira no-op (com aviso) |
42
+ | `endpoint` | `http://localhost:4050` | URL base da API |
43
+ | `serviceName` | — | vai em `attributes.serviceName` de todo span |
44
+ | `flushIntervalMs` | `2000` | intervalo de envio |
45
+ | `maxBatchSize` | `200` | ≥ N spans na fila dispara envio imediato |
46
+ | `maxQueueSize` | `5000` | acima disso descarta os spans mais antigos |
47
+ | `enabled` | `true` | `false` desliga tudo sem tirar o código |
48
+ | `debug` | `false` | loga cada envio |
49
+ | `release` | detectada | versão em execução (`FG_TRACKER_RELEASE`, `GITHUB_SHA`, `VERCEL_GIT_COMMIT_SHA`… → `${serviceName}@${sha}`); `false` desliga |
50
+ | `environment` | detectado | `FG_TRACKER_ENV`, `APP_ENV` ou `NODE_ENV`; `false` desliga |
51
+ | `propagateTraceTo` | `true` | destinos que recebem `traceparent` no fetch instrumentado (`true`, lista de hosts/prefixos/RegExp ou `false`) |
52
+ | `instrumentGlobalFetch` | `false` | substitui `globalThis.fetch` pelo fetch instrumentado (provedores externos viram spans) |
53
+ | `captureGlobalErrors` | `false` | `uncaughtExceptionMonitor` + `unhandledRejection` viram spans com erro |
54
+ | `tracesSampleRate` | `1` | fração de traces enviados (decidida na raiz; filhos seguem; `traceparent` sai com `-00` quando não amostrado) |
55
+ | `tracesSampler` | — | `(ctx: { kind, name, attributes, parentSampled? }) => taxa` por operação (ex.: `0` para `GET /health`) |
56
+ | `ignoreTransactions` | `[]` | nomes de raiz (string/RegExp) que nunca são enviados |
57
+ | `ignoreErrors` | `[]` | erros (`name: message`, string/RegExp) que não viram issue (`attributes.ignoredError = true`) |
58
+ | `scrubFields` | `DEFAULT_SCRUB_FIELDS` | chaves de atributos que viram `[Filtered]` (password, token, authorization, cookie, cpf…); `false` desliga |
59
+ | `beforeSend` | — | `(span) => span \| null`, último filtro antes da fila |
60
+
61
+ ## Erros, usuários, releases e jobs
62
+
63
+ ```ts
64
+ app.use(tracker.express()); // continua `traceparent` de outro serviço; associa req.user.id ao fim
65
+ app.use(routes);
66
+ app.use(tracker.expressErrorHandler()); // exceção real no span → issues agrupadas por causa, não por status
67
+
68
+ tracker.setUser({ id: user.id, email: user.email }); // impacto por usuário nas issues
69
+ tracker.captureException(err, { stage: 'checkout' }); // em filtros do Nest / catch de jobs
70
+ tracker.traceHeaders(); // { traceparent } para clientes HTTP não instrumentados
71
+ tracker.currentTraceId(); // para logs / x-trace-id
72
+
73
+ await tracker.checkIn('backup-diario', () => runBackup(), { intervalSeconds: 86400, graceSeconds: 600 }); // monitor de job
74
+ const run = await tracker.monitor('sync').start(); /* ... */ await run.ok(); // ou run.fail(err)
75
+
76
+ await tracker.registerRelease({ commitSha: process.env.GITHUB_SHA, repository: 'FORTGROUND/phase' }); // ou o CLI:
77
+ // FG_TRACKER_KEY=stk_... npx fg-tracker-release --service minha-app --previous-sha $PREV --env production
78
+ ```
79
+
80
+ O CLI `fg-tracker-release` lê `git log`/`git show` e envia commits, arquivos e intervalos de linhas alterados: o painel usa isso para apontar **commits/PRs suspeitos** em cada issue e comparar a latência de cada rota entre releases.
81
+
82
+ Jobs com crontab: `tracker.checkIn('relatorio', run, { cron: '0 3 * * *', timezone: 'America/Sao_Paulo', maxRuntimeSeconds: 1800 })` — o monitor calcula o próximo disparo no fuso, marca `timeout` quando passa do tempo máximo e aceita `failureThreshold`/`recoveryThreshold` na tela.
83
+
84
+ Source maps do front (para o `fg-tracker-browser`): `FG_TRACKER_KEY=stk_... npx fg-tracker-sourcemaps --dir dist --release web@$GITHUB_SHA --url-prefix ~/assets` sobe os `.map` de uma release; a API traduz o stack minificado na ingestão.
85
+
86
+ Falhas de envio são logadas **uma vez** e o lote é reenviado no próximo flush (respostas 4xx descartam o lote — normalmente é chave inválida). O timer usa `unref()`, então não segura o processo vivo; chame `shutdown()` no encerramento para não perder os últimos spans.
87
+
88
+ ## Express
89
+
90
+ ```ts
91
+ import express from 'express';
92
+ import { createTracker } from 'fg-tracker-sdk';
93
+
94
+ const tracker = createTracker({ apiKey: process.env.FG_TRACKER_KEY!, serviceName: 'minha-api' });
95
+ const app = express();
96
+ app.use(tracker.express()); // ANTES de qualquer outro middleware/rota
97
+ app.get('/users/:id', async (req, res) => { /* ... */ });
98
+
99
+ process.on('SIGTERM', () => tracker.shutdown().finally(() => process.exit(0)));
100
+ ```
101
+
102
+ O middleware cria um span raiz `http.server` por request. O nome é `${method} ${route}`; a rota vem de `req.baseUrl + req.route.path` (disponível no `finish` da resposta), com fallback para o path com uuids/números trocados por `:id`. Atributos: `url`, `userAgent`, `ip`. Status ≥ 500 vira `error`.
103
+
104
+ ## NestJS (platform-express)
105
+
106
+ ```ts
107
+ // src/main.ts
108
+ import { NestFactory } from '@nestjs/core';
109
+ import { createTracker } from 'fg-tracker-sdk';
110
+ import { AppModule } from './app.module';
111
+
112
+ export const tracker = createTracker({ apiKey: process.env.FG_TRACKER_KEY!, serviceName: 'minha-api' });
113
+
114
+ async function bootstrap() {
115
+ const app = await NestFactory.create(AppModule);
116
+ app.use(tracker.express()); // antes de helmet/cors/pipes
117
+ app.enableShutdownHooks();
118
+ await app.listen(4040);
119
+ }
120
+ bootstrap();
121
+ ```
122
+
123
+ Para encerrar limpo, chame `tracker.shutdown()` num `OnApplicationShutdown` (ou no handler de SIGTERM).
124
+
125
+ ## Next.js (App Router)
126
+
127
+ ```ts
128
+ // src/lib/tracker.ts
129
+ import { createTracker } from 'fg-tracker-sdk';
130
+ export const tracker = createTracker({ apiKey: process.env.FG_TRACKER_KEY!, serviceName: 'meu-site' });
131
+
132
+ // app/api/users/[id]/route.ts
133
+ import { tracker } from '@/lib/tracker';
134
+ export const GET = tracker.wrapNextRoute(async (req, ctx) => {
135
+ const user = await db.query('select * from users where id = $1', [id]);
136
+ return Response.json(user);
137
+ });
138
+ ```
139
+
140
+ O span `http.server` recebe `${method} ${pathname normalizado}` e o status da `Response`. Só funciona no runtime Node (não no Edge). Em `output: 'export'` não há route handlers dinâmicos, então nada a instrumentar.
141
+
142
+ ## pg
143
+
144
+ ```ts
145
+ import { Pool } from 'pg';
146
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
147
+ tracker.instrumentPg(pool);
148
+ ```
149
+
150
+ `pool.query` passa a gerar spans `db.query` **apenas quando há um span pai** no contexto (request em andamento ou `tracker.span`). O nome é o SQL normalizado — números → `?`, strings → `'?'`, `IN (...)` → `IN (?)`, espaços colapsados, minúsculas, 200 chars — e `attributes.rows` recebe o `rowCount`. Todas as assinaturas de `query` são suportadas (promise ou callback). Queries repetidas ≥ `loop_threshold` vezes no mesmo request viram uma issue **N_PLUS_ONE** na API.
151
+
152
+ Se você usa drizzle/kysely/knex sobre `pg.Pool`, instrumente o pool antes de passá-lo ao ORM.
153
+
154
+ ## fetch
155
+
156
+ ```ts
157
+ const fetchTracked = tracker.wrapFetch(); // envolve globalThis.fetch
158
+ const res = await fetchTracked('https://api.github.com/repos/FORTGROUND/fg-tracker');
159
+
160
+ // ou globalmente (cuidado: afeta todo o processo)
161
+ globalThis.fetch = tracker.wrapFetch(globalThis.fetch);
162
+ ```
163
+
164
+ Cada chamada vira um span `http.client` nomeado `${method} ${origin}${pathname normalizado}` (ex.: `GET https://api.github.com/repos/:id`). É isso que alimenta a detecção de **LOOP**: a mesma chamada repetida ≥ `loop_threshold` vezes sob o mesmo pai. Chamadas ao próprio endpoint do FG Tracker são ignoradas.
165
+
166
+ ## Spans manuais
167
+
168
+ ```ts
169
+ await tracker.span('custom', 'render-pdf', async (span) => {
170
+ span.setAttribute('pages', 12);
171
+ // exceções são capturadas em span.error e relançadas
172
+ });
173
+
174
+ // ou controle total
175
+ const span = tracker.startSpan('custom', 'warmup-cache');
176
+ try { /* ... */ } finally { span.end(); }
177
+
178
+ tracker.currentSpan()?.setAttribute('userId', user.id);
179
+ ```
180
+
181
+ ## Ingestão crua (qualquer linguagem)
182
+
183
+ Não precisa do SDK: basta enviar o body do contrato com a chave no header.
184
+
185
+ ```bash
186
+ curl -X POST http://localhost:4050/v1/ingest \
187
+ -H 'content-type: application/json' \
188
+ -H 'x-fg-tracker-key: stk_demo_0000000000000000000000000000' \
189
+ -d '{
190
+ "traces": [{
191
+ "traceId": "0123456789abcdef0123456789abcdef",
192
+ "spans": [
193
+ { "spanId": "0123456789abcdef", "parentSpanId": null, "kind": "http.server", "name": "GET /users/:id",
194
+ "method": "GET", "route": "/users/:id", "statusCode": 200,
195
+ "startedAt": "2026-09-12T12:00:00.000Z", "endedAt": "2026-09-12T12:00:00.123Z", "durationMs": 123.4,
196
+ "attributes": { "url": "/users/42" }, "error": null },
197
+ { "spanId": "fedcba9876543210", "parentSpanId": "0123456789abcdef", "kind": "db.query",
198
+ "name": "select * from users where id = ?", "method": null, "route": null, "statusCode": null,
199
+ "startedAt": "2026-09-12T12:00:00.010Z", "endedAt": "2026-09-12T12:00:00.050Z", "durationMs": 40,
200
+ "attributes": { "rows": 1 }, "error": null }
201
+ ]
202
+ }]
203
+ }'
204
+ # → 202 { "accepted": 2, "issuesDetected": 0 }
205
+ ```
206
+
207
+ ## Logs (`POST /v1/logs`)
208
+
209
+ O SDK envia linhas de log para a plataforma em lote (`flushIntervalMs` 2 s, `maxBatch` 200, fila de 5000 descartando as mais antigas, 5xx/rede tenta de novo uma vez e devolve o lote ao próximo flush, 4xx descarta). Quando a linha nasce dentro de um span do tracker (request Express/Nest, `tracker.span`), `traceId`/`spanId` são anexados automaticamente e a linha aparece ligada ao trace no explorador `/logs`.
210
+
211
+ ### Shipper cru
212
+
213
+ ```ts
214
+ import { createLogShipper } from 'fg-tracker-sdk';
215
+
216
+ const logs = createLogShipper({ apiKey: process.env.FG_TRACKER_KEY!, endpoint: 'http://localhost:4050', service: 'minha-api', env: 'production', tracker });
217
+ logs.push({ level: 'info', message: 'user logged in', context: { userId: 42 }, requestId: req.id });
218
+ logs.log('error', 'payment failed', { orderId });
219
+ await logs.flush(); await logs.shutdown();
220
+ ```
221
+
222
+ | opção | default | descrição |
223
+ |---|---|---|
224
+ | `apiKey` / `endpoint` | — / `http://localhost:4050` | iguais ao tracker |
225
+ | `service` | — | nome do serviço (obrigatório) |
226
+ | `env`, `host` | — / `os.hostname()` | ambiente e host; `host: false` desliga |
227
+ | `flushIntervalMs`, `maxBatch`, `maxQueueSize` | `2000`, `200`, `5000` | lote e fila |
228
+ | `tracker` | — | correlação com o span corrente (sem ele usa o contexto global do SDK, que funciona quando o tracker está no mesmo processo) |
229
+ | `minLevel` | `trace` | nível mínimo enviado |
230
+
231
+ ### pino (pino-http, Fastify, NestJS com nestjs-pino)
232
+
233
+ Transport v7+ via `pino-abstract-transport` — roda em worker thread. Por isso o transport **não** enxerga o `AsyncLocalStorage` da aplicação: use `traceMixin` para carimbar `traceId`/`spanId` na thread principal.
234
+
235
+ ```ts
236
+ import pino from 'pino';
237
+ import pinoHttp from 'pino-http';
238
+ import { pinoTransport, traceMixin } from 'fg-tracker-sdk';
239
+
240
+ const logger = pino({
241
+ level: 'info',
242
+ mixin: traceMixin(tracker), // opcional; correlação com traces
243
+ transport: pinoTransport({ apiKey: process.env.FG_TRACKER_KEY!, endpoint: 'http://localhost:4050', service: 'minha-api', env: 'production' }),
244
+ // equivalente: transport: { target: 'fg-tracker-sdk/pino', options: { apiKey, service } }
245
+ });
246
+ app.use(pinoHttp({ logger })); // req.id vira requestId
247
+ logger.info({ userId: 42 }, 'user logged in');
248
+ ```
249
+
250
+ Mapeamento: `level` numérico → nível, `msg` → `message`, `name` → `service` (quando `service` não é passado), `hostname` → `host`, `req.id`/`reqId`/`requestId` → `requestId`, `env` → `env`; todas as outras chaves vão para `context`. Para enviar múltiplos destinos use `pino.transport({ targets: [{ target: 'pino-pretty' }, pinoTransport({...})] })`.
251
+
252
+ ### winston
253
+
254
+ ```ts
255
+ import winston from 'winston';
256
+ import { FgTrackerTransport } from 'fg-tracker-sdk/winston';
257
+
258
+ const logger = winston.createLogger({
259
+ level: 'info',
260
+ format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
261
+ transports: [
262
+ new winston.transports.Console(),
263
+ new FgTrackerTransport({ apiKey: process.env.FG_TRACKER_KEY!, service: 'minha-api', tracker }),
264
+ ],
265
+ });
266
+ logger.warn('stock low', { sku: 'A-1' }); // meta → context
267
+ ```
268
+
269
+ `winston-transport` é peer opcional (vem com o winston). Níveis npm/syslog fora do contrato são aproximados (`crit`/`alert`/`emerg` → `fatal`, `verbose`/`silly` → `debug`, `http`/`notice` → `info`). A instância expõe `transport.shipper` para `flush()`/`shutdown()`.
270
+
271
+ ### NestJS Logger
272
+
273
+ Implementa a forma do `LoggerService` sem depender de `@nestjs/common`: espelha no console (desligue com `console: false`) e envia. O último parâmetro string vira `context.context` (nome da classe), e em `error(msg, stack, context)` o stack vai em `context.stack`.
274
+
275
+ ```ts
276
+ // src/main.ts
277
+ import { NestFactory } from '@nestjs/core';
278
+ import { createTracker, nestLogger } from 'fg-tracker-sdk';
279
+
280
+ export const tracker = createTracker({ apiKey: process.env.FG_TRACKER_KEY!, serviceName: 'minha-api' });
281
+ export const logger = nestLogger({ apiKey: process.env.FG_TRACKER_KEY!, service: 'minha-api', tracker });
282
+
283
+ const app = await NestFactory.create(AppModule, { logger });
284
+ app.use(tracker.express());
285
+ app.enableShutdownHooks();
286
+ process.on('SIGTERM', () => Promise.all([logger.shutdown(), tracker.shutdown()]).finally(() => process.exit(0)));
287
+ ```
288
+
289
+ Dentro das classes continue usando `new Logger(MinhaClasse.name)` do Nest — ele delega ao logger da aplicação.
290
+
291
+ ### Ingestão crua de logs (qualquer linguagem)
292
+
293
+ ```bash
294
+ curl -X POST http://localhost:4050/v1/logs \\
295
+ -H 'content-type: application/json' \\
296
+ -H 'x-fg-tracker-key: stk_demo_0000000000000000000000000000' \\
297
+ -d '{ "logs": [
298
+ { "ts": "2026-09-12T12:00:00.000Z", "level": "info", "service": "minha-api", "message": "user logged in",
299
+ "context": { "userId": 42 }, "traceId": "0123456789abcdef0123456789abcdef", "requestId": "req-1", "env": "production" },
300
+ { "level": "error", "service": "worker", "message": "ECONNRESET while calling payment gateway" }
301
+ ] }'
302
+ # → 202 { "accepted": 2 }
303
+ ```
304
+
305
+ Limites: 500 linhas por lote, 32 KB por mensagem, `context` truncado a 8 KB, `ts` fora da retenção vira `now()`.
306
+
307
+ ## Variáveis de ambiente
308
+
309
+ | variável | uso |
310
+ |---|---|
311
+ | `FG_TRACKER_KEY` | chave do projeto (CLIs; nos exemplos, `apiKey`) |
312
+ | `FG_TRACKER_URL` | URL da API (CLIs; nos exemplos, `endpoint`) |
313
+ | `FG_TRACKER_RELEASE` | release explícita (tracker e `fg-tracker-sourcemaps`) |
314
+ | `FG_TRACKER_ENV` | ambiente (tracker e `fg-tracker-release`) |
315
+ | `FG_TRACKER_SERVICE` | serviço padrão do `fg-tracker-release` |
316
+
317
+ O SDK lê o nome novo primeiro e, se estiver ausente ou vazio, o nome legado `STREAD_TRACK_*`. `readEnv('KEY')` expõe a mesma regra para o seu código.
318
+
319
+ ## Migração de stread-track-sdk
320
+
321
+ O pacote foi renomeado de `stread-track-sdk` (0.3.0) para `fg-tracker-sdk` (0.4.0). **Nada quebra**: os nomes antigos continuam funcionando e serão removidos na 1.0.
322
+
323
+ | antigo | novo | compatibilidade |
324
+ |---|---|---|
325
+ | pacote `stread-track-sdk` | `fg-tracker-sdk` | a 0.3.0 antiga segue no npm e continua enviando para a API |
326
+ | bins `stread-track-release`, `stread-track-sourcemaps` | `fg-tracker-release`, `fg-tracker-sourcemaps` | os bins antigos são aliases obsoletos (mesmo arquivo, com aviso no stderr) |
327
+ | `STREAD_TRACK_KEY`, `STREAD_TRACK_URL`, `STREAD_TRACK_RELEASE`, `STREAD_TRACK_ENV`, `STREAD_TRACK_SERVICE` | `FG_TRACKER_KEY`, `FG_TRACKER_URL`, `FG_TRACKER_RELEASE`, `FG_TRACKER_ENV`, `FG_TRACKER_SERVICE` | o novo tem precedência; o antigo é fallback |
328
+ | header `x-stread-track-key` | `x-fg-tracker-key` | a API aceita os dois (e `?key=`) |
329
+ | `StreadTrackTransport` (`/winston`) | `FgTrackerTransport` | re-export `@deprecated` |
330
+ | tipo `StreadTrackNestLogger` | `FgTrackerNestLogger` | alias `@deprecated` |
331
+ | pino `target: 'stread-track-sdk/pino'` | `target: 'fg-tracker-sdk/pino'` | troque junto com o nome do pacote |
332
+ | prefixo de log `[stread-track]` | `[fg-tracker]` | — |
333
+
334
+ Passo a passo: `pnpm remove stread-track-sdk && pnpm add fg-tracker-sdk@^0.4.0`, troque os imports (`from 'stread-track-sdk'` → `from 'fg-tracker-sdk'`, inclusive `/pino` e `/winston`) e, quando quiser, renomeie as variáveis de ambiente e os bins no CI. A API precisa aceitar `x-fg-tracker-key` (versão da API a partir desta mudança) — a produção do FG Tracker já aceita.
335
+
336
+ ## Build
337
+
338
+ ```bash
339
+ pnpm --filter fg-tracker-sdk build # tsc → dist/ (ESM + .d.ts)
340
+ pnpm --filter fg-tracker-sdk typecheck
341
+ ```
342
+
343
+ ## Database adapters (0.2.0)
344
+
345
+ Reuse the HTTP request tracker. `instrumentPostgres(sql)` wraps Postgres.js lazy
346
+ queries, Drizzle's unsafe/values path, transactions, savepoints and reserved
347
+ connections. Do not also instrument Drizzle for the same operation. It records
348
+ normalized SQL and execution duration, including pool wait, with the request's
349
+ trace ID and current parent span. Reusing a pending query does not execute or
350
+ record it twice. Streaming/cursor helpers need an explicit completion wrapper.
351
+
352
+ `instrumentDatabase(client, { system, methods, statement?, callbacks? })` supports
353
+ methods returning synchronous results, Promises, or final error-first callbacks.
354
+ Instrument acquired transaction connections separately. Only provide `statement`
355
+ for SQL, never bind values, MongoDB documents, Redis keys or connection URLs.
356
+ This adapter cannot infer arbitrary cursor/stream completion; wrap consumption:
357
+
358
+ ```ts
359
+ await tracker.traceDatabase(
360
+ { system: 'mongodb', operation: 'tracks.find' },
361
+ () => collection.find(filter).toArray(),
362
+ );
363
+ const row = tracker.traceDatabaseSync(
364
+ { system: 'sqlite', operation: 'tracks.get', statement: 'select * from tracks where id = ?' },
365
+ () => statement.get(id),
366
+ );
367
+ ```
368
+
369
+ These completion wrappers work with any engine, without installing its driver in
370
+ the SDK. Unknown drivers require explicit binding at the operation boundary;
371
+ there is no universal automatic monkey patch. Operations outside an active trace
372
+ are executed normally without orphan query traces. Database error values remain
373
+ in the application; exports contain a generic error and an optional error code.
374
+ Use `track_detect_databases` in the MCP to select the adapter from the actual
375
+ application dependencies/imports and then implement it at the active client.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../src/cli/release.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI `fg-tracker-release` (alias obsoleto: `stread-track-release`): registra uma release no FG Tracker a partir do git
4
+ * (commits e arquivos/linhas alterados desde a versão anterior), para o painel
5
+ * apontar commits e PRs suspeitos de cada erro (CONTRATO.md §15).
6
+ *
7
+ * FG_TRACKER_KEY=stk_... FG_TRACKER_URL=https://<api-do-fg-tracker> \
8
+ * npx fg-tracker-release --service minha-api --previous-sha $PREV_SHA --repo FORTGROUND/phase --env production
9
+ *
10
+ * Flags: --version (default `${service}@${HEAD}`), --service, --sha (default HEAD),
11
+ * --previous-sha (default: 20 commits anteriores), --previous-version, --repo, --env,
12
+ * --url, --notes, --endpoint, --api-key, --dry-run.
13
+ * Env: FG_TRACKER_KEY, FG_TRACKER_URL, FG_TRACKER_SERVICE, FG_TRACKER_ENV
14
+ * (os nomes legados STREAD_TRACK_* continuam valendo como fallback).
15
+ */
16
+ import { execFileSync } from 'node:child_process';
17
+ import { API_KEY_HEADER, DEFAULT_ENDPOINT, readEnv, warnLegacyBin } from '../constants.js';
18
+ function parseArgs(argv) {
19
+ const args = {};
20
+ for (let index = 0; index < argv.length; index += 1) {
21
+ const item = argv[index];
22
+ if (!item.startsWith('--'))
23
+ continue;
24
+ const key = item.slice(2);
25
+ const next = argv[index + 1];
26
+ if (next === undefined || next.startsWith('--'))
27
+ args[key] = true;
28
+ else {
29
+ args[key] = next;
30
+ index += 1;
31
+ }
32
+ }
33
+ return args;
34
+ }
35
+ function git(...params) {
36
+ return execFileSync('git', params, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim();
37
+ }
38
+ function filesOf(sha) {
39
+ const files = new Map();
40
+ const numstat = git('show', '--numstat', '--format=', sha);
41
+ for (const line of numstat.split('\n')) {
42
+ const [added, deleted, path] = line.split('\t');
43
+ if (!path)
44
+ continue;
45
+ files.set(path, { path, additions: Number(added) || 0, deletions: Number(deleted) || 0, ranges: [] });
46
+ }
47
+ const diff = git('show', '--unified=0', '--format=', sha);
48
+ let current;
49
+ for (const line of diff.split('\n')) {
50
+ if (line.startsWith('+++ ')) {
51
+ const path = line.slice(4).replace(/^b\//, '');
52
+ current = files.get(path);
53
+ continue;
54
+ }
55
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
56
+ if (hunk && current) {
57
+ const start = Number(hunk[1]);
58
+ const count = hunk[2] === undefined ? 1 : Number(hunk[2]);
59
+ current.ranges.push([start, Math.max(start, start + count - 1)]);
60
+ }
61
+ }
62
+ return [...files.values()];
63
+ }
64
+ async function main() {
65
+ warnLegacyBin('fg-tracker-release');
66
+ const args = parseArgs(process.argv.slice(2));
67
+ const apiKey = (typeof args['api-key'] === 'string' ? args['api-key'] : readEnv('KEY')) ?? '';
68
+ const endpoint = ((typeof args.endpoint === 'string' ? args.endpoint : readEnv('URL')) ?? DEFAULT_ENDPOINT).replace(/\/+$/, '');
69
+ if (!apiKey && !args['dry-run']) {
70
+ console.error('FG_TRACKER_KEY (ou --api-key) é obrigatório.');
71
+ process.exit(2);
72
+ }
73
+ const sha = typeof args.sha === 'string' ? args.sha : git('rev-parse', 'HEAD');
74
+ const service = typeof args.service === 'string' ? args.service : readEnv('SERVICE') ?? 'app';
75
+ const version = typeof args.version === 'string' ? args.version : `${service}@${sha}`;
76
+ const previousSha = typeof args['previous-sha'] === 'string' ? args['previous-sha'] : undefined;
77
+ const range = previousSha ? `${previousSha}..${sha}` : `${sha}~20..${sha}`;
78
+ let log = '';
79
+ try {
80
+ log = git('log', '--format=%H%x1f%an%x1f%aI%x1f%B%x1e', range);
81
+ }
82
+ catch {
83
+ log = git('log', '--format=%H%x1f%an%x1f%aI%x1f%B%x1e', '-n', '20', sha);
84
+ }
85
+ const commits = log
86
+ .split('\x1e')
87
+ .map((entry) => entry.trim())
88
+ .filter(Boolean)
89
+ .map((entry) => {
90
+ const [hash, author, authoredAt, message] = entry.split('\x1f');
91
+ return { sha: hash, author, authoredAt, message: (message ?? '').trim(), files: filesOf(hash) };
92
+ });
93
+ const repository = typeof args.repo === 'string' ? args.repo : detectRepository();
94
+ const body = {
95
+ version,
96
+ environment: typeof args.env === 'string' ? args.env : readEnv('ENV') ?? process.env.NODE_ENV,
97
+ commitSha: sha,
98
+ previousVersion: typeof args['previous-version'] === 'string' ? args['previous-version'] : previousSha ? `${service}@${previousSha}` : undefined,
99
+ repository,
100
+ url: typeof args.url === 'string' ? args.url : undefined,
101
+ notes: typeof args.notes === 'string' ? args.notes : undefined,
102
+ commits,
103
+ };
104
+ if (args['dry-run']) {
105
+ console.log(JSON.stringify(body, null, 2));
106
+ return;
107
+ }
108
+ const res = await fetch(`${endpoint}/v1/releases`, {
109
+ method: 'POST',
110
+ headers: { 'content-type': 'application/json', [API_KEY_HEADER]: apiKey },
111
+ body: JSON.stringify(body),
112
+ signal: AbortSignal.timeout(20_000),
113
+ });
114
+ if (!res.ok) {
115
+ console.error(`Falha ao registrar release (${res.status}): ${await res.text()}`);
116
+ process.exit(1);
117
+ }
118
+ console.log(`Release ${version} registrada (${commits.length} commits, ${new Set(commits.flatMap((c) => c.files.map((f) => f.path))).size} arquivos).`);
119
+ }
120
+ function detectRepository() {
121
+ try {
122
+ const remote = git('config', '--get', 'remote.origin.url');
123
+ const match = /github\.com[/:]([^/\s]+)\/([^/\s]+?)(?:\.git)?$/.exec(remote);
124
+ return match ? `${match[1]}/${match[2]}` : undefined;
125
+ }
126
+ catch {
127
+ return undefined;
128
+ }
129
+ }
130
+ main().catch((error) => {
131
+ console.error(error instanceof Error ? error.message : String(error));
132
+ process.exit(1);
133
+ });
134
+ //# sourceMappingURL=release.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.js","sourceRoot":"","sources":["../../src/cli/release.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=sourcemaps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourcemaps.d.ts","sourceRoot":"","sources":["../../src/cli/sourcemaps.ts"],"names":[],"mappings":""}
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI `fg-tracker-sourcemaps` (alias obsoleto: `stread-track-sourcemaps`): envia os `.map` de um build para o FG Tracker
4
+ * (`POST /v1/sourcemaps`), para os stacks do navegador chegarem simbolizados
5
+ * (CONTRATO.md §24).
6
+ *
7
+ * FG_TRACKER_KEY=stk_... FG_TRACKER_URL=https://<api-do-fg-tracker> \
8
+ * npx fg-tracker-sourcemaps --dir dist --release web@$GITHUB_SHA --url-prefix ~/assets
9
+ *
10
+ * Flags: --dir (obrigatório), --release (default: FG_TRACKER_RELEASE; legado STREAD_TRACK_RELEASE), --url-prefix
11
+ * (caminho público onde os JS ficam; `~` = qualquer host; default `~`), --endpoint,
12
+ * --api-key, --dry-run.
13
+ */
14
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
15
+ import { join, relative } from 'node:path';
16
+ import { API_KEY_HEADER, DEFAULT_ENDPOINT, readEnv, warnLegacyBin } from '../constants.js';
17
+ function parseArgs(argv) {
18
+ const args = {};
19
+ for (let i = 0; i < argv.length; i += 1) {
20
+ const item = argv[i];
21
+ if (!item.startsWith('--'))
22
+ continue;
23
+ const next = argv[i + 1];
24
+ if (next === undefined || next.startsWith('--'))
25
+ args[item.slice(2)] = true;
26
+ else {
27
+ args[item.slice(2)] = next;
28
+ i += 1;
29
+ }
30
+ }
31
+ return args;
32
+ }
33
+ function walk(dir, out = []) {
34
+ for (const entry of readdirSync(dir)) {
35
+ const full = join(dir, entry);
36
+ if (statSync(full).isDirectory())
37
+ walk(full, out);
38
+ else if (entry.endsWith('.map'))
39
+ out.push(full);
40
+ }
41
+ return out;
42
+ }
43
+ async function main() {
44
+ warnLegacyBin('fg-tracker-sourcemaps');
45
+ const args = parseArgs(process.argv.slice(2));
46
+ const dir = typeof args.dir === 'string' ? args.dir : '';
47
+ if (!dir) {
48
+ console.error('--dir é obrigatório (pasta do build com os arquivos .map)');
49
+ process.exit(2);
50
+ }
51
+ const release = typeof args.release === 'string' ? args.release : readEnv('RELEASE');
52
+ if (!release) {
53
+ console.error('--release (ou FG_TRACKER_RELEASE) é obrigatório e deve ser o mesmo `release` do SDK de navegador');
54
+ process.exit(2);
55
+ }
56
+ const apiKey = (typeof args['api-key'] === 'string' ? args['api-key'] : readEnv('KEY')) ?? '';
57
+ const endpoint = ((typeof args.endpoint === 'string' ? args.endpoint : readEnv('URL')) ?? DEFAULT_ENDPOINT).replace(/\/+$/, '');
58
+ const prefix = (typeof args['url-prefix'] === 'string' ? args['url-prefix'] : '~').replace(/\/+$/, '');
59
+ const maps = walk(dir);
60
+ if (maps.length === 0) {
61
+ console.error(`Nenhum .map encontrado em ${dir}`);
62
+ process.exit(1);
63
+ }
64
+ const files = maps.map((file) => ({
65
+ path: `${prefix}/${relative(dir, file).replace(/\\/g, '/').replace(/\.map$/, '')}`,
66
+ map: readFileSync(file, 'utf8'),
67
+ }));
68
+ if (args['dry-run']) {
69
+ for (const file of files)
70
+ console.log(`${file.path} (${Math.round(file.map.length / 1024)} KB)`);
71
+ return;
72
+ }
73
+ if (!apiKey) {
74
+ console.error('FG_TRACKER_KEY (ou --api-key) é obrigatório.');
75
+ process.exit(2);
76
+ }
77
+ let accepted = 0;
78
+ for (let i = 0; i < files.length; i += 20) {
79
+ const batch = files.slice(i, i + 20);
80
+ const res = await fetch(`${endpoint}/v1/sourcemaps`, {
81
+ method: 'POST',
82
+ headers: { 'content-type': 'application/json', [API_KEY_HEADER]: apiKey },
83
+ body: JSON.stringify({ release, files: batch }),
84
+ signal: AbortSignal.timeout(60_000),
85
+ });
86
+ if (!res.ok) {
87
+ console.error(`Falha no upload (${res.status}): ${await res.text()}`);
88
+ process.exit(1);
89
+ }
90
+ accepted += (await res.json()).accepted;
91
+ }
92
+ console.log(`${accepted} source maps enviados para a release ${release}.`);
93
+ }
94
+ main().catch((error) => {
95
+ console.error(error instanceof Error ? error.message : String(error));
96
+ process.exit(1);
97
+ });
98
+ //# sourceMappingURL=sourcemaps.js.map