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.
- package/CHANGELOG.md +17 -0
- package/README.md +375 -0
- package/dist/cli/release.d.ts +3 -0
- package/dist/cli/release.d.ts.map +1 -0
- package/dist/cli/release.js +134 -0
- package/dist/cli/release.js.map +1 -0
- package/dist/cli/sourcemaps.d.ts +3 -0
- package/dist/cli/sourcemaps.d.ts.map +1 -0
- package/dist/cli/sourcemaps.js +98 -0
- package/dist/cli/sourcemaps.js.map +1 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +35 -0
- package/dist/constants.js.map +1 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +42 -0
- package/dist/context.js.map +1 -0
- package/dist/database.d.ts +29 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +157 -0
- package/dist/database.js.map +1 -0
- package/dist/express.d.ts +17 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +125 -0
- package/dist/express.js.map +1 -0
- package/dist/fetch.d.ts +9 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +78 -0
- package/dist/fetch.js.map +1 -0
- package/dist/global-errors.d.ts +11 -0
- package/dist/global-errors.d.ts.map +1 -0
- package/dist/global-errors.js +35 -0
- package/dist/global-errors.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/logs/nest-logger.d.ts +40 -0
- package/dist/logs/nest-logger.d.ts.map +1 -0
- package/dist/logs/nest-logger.js +99 -0
- package/dist/logs/nest-logger.js.map +1 -0
- package/dist/logs/pino.d.ts +24 -0
- package/dist/logs/pino.d.ts.map +1 -0
- package/dist/logs/pino.js +62 -0
- package/dist/logs/pino.js.map +1 -0
- package/dist/logs/shipper.d.ts +18 -0
- package/dist/logs/shipper.d.ts.map +1 -0
- package/dist/logs/shipper.js +217 -0
- package/dist/logs/shipper.js.map +1 -0
- package/dist/logs/types.d.ts +84 -0
- package/dist/logs/types.d.ts.map +1 -0
- package/dist/logs/types.js +47 -0
- package/dist/logs/types.js.map +1 -0
- package/dist/logs/winston.d.ts +31 -0
- package/dist/logs/winston.d.ts.map +1 -0
- package/dist/logs/winston.js +57 -0
- package/dist/logs/winston.js.map +1 -0
- package/dist/monitors.d.ts +8 -0
- package/dist/monitors.d.ts.map +1 -0
- package/dist/monitors.js +69 -0
- package/dist/monitors.js.map +1 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +55 -0
- package/dist/next.js.map +1 -0
- package/dist/normalize.d.ts +12 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +113 -0
- package/dist/normalize.js.map +1 -0
- package/dist/pg.d.ts +9 -0
- package/dist/pg.d.ts.map +1 -0
- package/dist/pg.js +83 -0
- package/dist/pg.js.map +1 -0
- package/dist/releases.d.ts +5 -0
- package/dist/releases.d.ts.map +1 -0
- package/dist/releases.js +30 -0
- package/dist/releases.js.map +1 -0
- package/dist/scrub.d.ts +11 -0
- package/dist/scrub.d.ts.map +1 -0
- package/dist/scrub.js +34 -0
- package/dist/scrub.js.map +1 -0
- package/dist/tracker.d.ts +99 -0
- package/dist/tracker.d.ts.map +1 -0
- package/dist/tracker.js +400 -0
- package/dist/tracker.js.map +1 -0
- package/dist/types.d.ts +264 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|