Documentação · Node.js
Documentação
SDK Node.js
fg-tracker-sdk com Express, NestJS, route handlers do Next.js, Postgres, chamadas HTTP, logs, erros, amostragem e encerramento.
Conferido com o código e os pacotes publicados em 2026-10-16
O fg-tracker-sdk (0.11.0) cria um span http.server por requisição, spans db.query para as queries e http.client para as chamadas de saída, encadeados por AsyncLocalStorage, e envia tudo em lote para a API. Ele só instrumenta: não roda detectores nem guarda dados; isso é trabalho da API do FG Tracker.
Instalação e requisitos
npm install fg-tracker-sdk- Node.js 20 ou mais novo. O pacote é só ESM.
- Projetos CommonJS (o padrão do NestJS) conseguem fazer
require('fg-tracker-sdk')a partir do Node 22.12. No Node 20, useawait import('fg-tracker-sdk'). - Não é preciso carregar o SDK antes dos outros imports: nada é instrumentado por hook de
require. O que importa é a ordem dos middlewares e instrumentar o pool antes de entregá-lo ao ORM.
Configuração
Crie um tracker por processo, num módulo próprio, e importe-o onde precisar:
// src/tracker.js: um tracker por processo, importado onde for preciso
import { createTracker } from 'fg-tracker-sdk';
export const tracker = createTracker({
apiKey: process.env.FG_TRACKER_KEY, // fgsk_…
endpoint: process.env.FG_TRACKER_URL, // https://tracker-api.fortground.com.br
serviceName: 'checkout-api',
release: process.env.GIT_SHA, // opcional: versão em execução
});| Opção | Padrão | O que faz |
|---|---|---|
apiKey | — | Chave de servidor fgsk_…. Vazia, o tracker desliga e avisa no console. |
endpoint | http://localhost:4050 | URL base da API, sem /v1. Em produção, passe sempre. |
serviceName | — | Nome do serviço em todos os spans. |
environment | FG_TRACKER_ENV, APP_ENV ou NODE_ENV | Ambiente dos spans. false desliga. |
release | FG_TRACKER_RELEASE ou o SHA do CI | Versão em execução, usada para comparar releases. |
tracesSampleRate | 1 | Fração de traces enviados. |
instrumentGlobalFetch | false | Liga spans para o fetch do Node. |
propagateTraceTo | true | Destinos que recebem o header traceparent. |
captureGlobalErrors | false | uncaughtException e unhandledRejection viram spans com erro. |
scrubFields | password, token, authorization, cookie… | Chaves de atributo trocadas por [Filtered]. CPF e e-mail não entram na lista padrão: acrescente se precisar. |
beforeSend | — | Último filtro: devolva o span alterado ou null para descartar. |
debug | false | Mostra cada envio no console. |
enabled | true | false desliga tudo sem tirar o código. |
A lista completa de opções está no README do pacote no npm.
Projeto, serviço e ambiente
- Projeto: definido pela chave. Não há campo de projeto no SDK.
- Serviço:
serviceName. Use um nome por aplicação (checkout-api,checkout-worker); o trace distribuído mostra os serviços que participaram. - Ambiente:
environment, texto livre de até 64 caracteres (production,staging,development). Span sem ambiente fica fora dos filtros e dos SLOs por ambiente. - Release:
release, para comparar latência e erros entre versões.
Express
app.use(tracker.express()) deve ser o primeiro middleware. O span recebe o nome MÉTODO /rota com os parâmetros da rota (/users/:id), status 5xx vira erro, o traceparent recebido é continuado e a resposta ganha o header x-trace-id. Testado com Express 5; o exemplo completo está no início rápido.
NestJS
Funciona com a plataforma Express do Nest (@nestjs/platform-express, o padrão). Não há módulo nem decorator: o middleware entra no bootstrap. Fastify não é suportado.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { tracker } from './tracker';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(tracker.express()); // antes de helmet, cors e pipes
app.enableShutdownHooks(); // o Nest recebe SIGTERM e encerra os módulos
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
// Em algum provider: chame tracker.shutdown() no encerramento
// async onApplicationShutdown() { await tracker.shutdown(); }Para ver métodos lentos de services e controllers no waterfall sem instrumentar um por um, chame tracker.instrumentNest(app.get(ModulesContainer)) depois de criar o app (ModulesContainer vem de @nestjs/core). Só métodos acima de 5 ms viram span.
Next.js (route handlers)
O suporte cobre os route handlers do App Router no runtime Node. Server components, server actions, middleware, Pages Router e Edge não são instrumentados.
// app/api/orders/[id]/route.ts (App Router, runtime Node)
import { tracker } from '@/lib/tracker';
export const runtime = 'nodejs';
export const GET = tracker.wrapNextRoute(async (req: Request) => {
return Response.json({ ok: true });
});O código do navegador do mesmo app usa outro pacote e outra chave: veja Navegador → Next.js.
Postgres e outros bancos
import pg from 'pg';
import { tracker } from './tracker.js';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
tracker.instrumentPg(pool); // antes de entregar o pool ao ORM (drizzle, kysely, knex)- Cada
pool.querydentro de uma requisição vira um spandb.querycom o SQL normalizado (valores trocados por?). É isso que alimenta a detecção de N+1: a mesma query 10 vezes ou mais sob o mesmo pai vira uma issue. - Queries fora de uma requisição (sem span pai) não geram span. Para um job, envolva o trabalho em
tracker.span('custom', …). pool.connect()vira o spanpg connect(com a espera por conexão), as queries do client devolvido viramdb.query, eBEGIN … COMMIT/ROLLBACKvirapg transaction, pai das queries.postgres(porsager):tracker.instrumentPostgres(sql). Outros drivers:tracker.traceDatabase({ system, operation }, fn).
Chamadas HTTP e propagação
- Saídas por
node:http/node:https(AWS SDK v3, axios, got, node-fetch 2, googleapis) viram spanshttp.clientsem configuração. - O
fetchnativo do Node não é coberto por padrão: ligueinstrumentGlobalFetch: trueou usetracker.wrapFetch(). - Por padrão, o header
traceparentvai para qualquer destino. Para não mandar ids de trace a terceiros, restrinja aos seus domínios compropagateTraceTo: um texto casa o host exato ou qualquer subdomínio dele ('suaempresa.com.br'cobreapi.suaempresa.com.br), um texto que começa comhttps://é prefixo da URL, e uma RegExp é testada contra a URL inteira, com caminho (não ancore em$depois do domínio).
createTracker({
// ...
instrumentGlobalFetch: true, // fetch do Node vira span http.client
// hosts exatos ou sufixo de domínio ('interno…' cobre orders.interno.suaempresa.com.br);
// RegExp é testada contra a URL inteira (com caminho), não só o host
propagateTraceTo: ['api.suaempresa.com.br', 'interno.suaempresa.com.br'],
});Do outro lado, o serviço que recebe a chamada continua o mesmo trace se também usar o SDK (o middleware lê o traceparent). O resultado é um trace distribuído com os dois serviços.
Filas e eventos (SQS, EventBridge)
A partir da versão 0.11.0, um client da AWS instrumentado leva o contexto do trace nas mensagens, e o consumidor continua o mesmo trace:
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
import { tracker } from './tracker.js';
// Produtor: dentro de um trace, a mensagem leva traceparent nos MessageAttributes.
const sqs = tracker.instrumentAws(new SQSClient({}));
await sqs.send(new SendMessageCommand({ QueueUrl, MessageBody: JSON.stringify(pedido) }));
// Consumidor em Lambda: cada mensagem continua o trace de quem a publicou.
export const handler = tracker.wrapLambda(async (event) => {
await Promise.all(event.Records.map((record) =>
tracker.trackMessage(QueueUrl, record, () => processar(record)),
));
});- SQS:
SendMessageeSendMessageBatchganham o spanSQS publish <fila>e o atributo de mensagemtraceparent, dentro das cotas do SQS (10 atributos, 1 MiB). Sem espaço, a mensagem segue como você a montou; a publicação nunca falha por causa da instrumentação. Em fila FIFO, informesqsMaxMessageBytesnoinstrumentAwspara propagar em mensagens acima de 1 KiB. - Consumidor:
trackMessage(fila, record, fn)aceita o record do evento da Lambda ou a mensagem doReceiveMessagee mede a latência desde o envio e as tentativas.trackMessage(fila, fn), sem o record, mede só localmente. - EventBridge:
PutEventsganhatraceparentdentro doDetail(quando ele é um objeto JSON). Consumidores que validam um schema estrito passam a ver essa chave; nesse client, useinstrumentAws(client, { propagation: false }). wrapLambdacontinua o trace de eventos de origem única (um record SQS, EventBridge comdetail.traceparent, HTTP comtraceparent); um lote de mensagens fica num trace próprio, com links para cada produtor.
Spans manuais
await tracker.span('custom', 'gerar-pdf', async (span) => {
span.setAttribute('paginas', 12); // exceções viram erro do span e são relançadas
});
const lento = await tracker.measure('resolverAvatares', () => carregarAvatares(ids)); // só vira span acima de 5 msspan cria o span sempre e vira pai do que acontece dentro. measure só registra trechos lentos e não vira pai. Para controle total, use tracker.startSpan(kind, nome) e chame end(). Um tracker.span('custom', …) sem pai é um job no painel.
Erros e usuários
app.use(tracker.express());
app.use(rotas);
app.use(tracker.expressErrorHandler()); // depois das rotas: a exceção real vai para o span
tracker.setUser({ id: usuario.id }); // impacto por usuário nas issues
tracker.captureException(err, { etapa: 'checkout' }); // erros tratados em jobs ou filtros- Status 5xx sempre marca erro. Com
expressErrorHandler(), a issue é agrupada pela exceção real (nome, mensagem e arquivo), não só pelo status. ignoreErrors(texto ou RegExp sobreNome: mensagem) mantém o span mas não abre issue.
Amostragem
createTracker({
// ...
tracesSampleRate: 0.2, // 20% dos traces, decidido uma vez na raiz
// respectUpstreamSampling: false, // ignora a decisão de quem chamou (traceparent ...-00)
sendUnsampledErrors: true, // envia o span com erro (e a raiz dele) de um trace descartado
});
// Ou por operação: tracesSampler vence tracesSampleRate e precisa devolver sempre um número
// (ou boolean); qualquer outro valor descarta o trace.
createTracker({
// ...
tracesSampler: ({ name }) => (name === 'GET /health' ? 0 : 0.2),
});- A decisão é por trace, na raiz local. Um trace que chega com decisão no
traceparentsegue a decisão de quem chamou. - Erros não são preservados por padrão. Com
sendUnsampledErrors: true, o span que falhou e a raiz dele são enviados, marcados como preservados; o painel os mostra em traces e issues, mas não os conta nas métricas de volume e latência. - Spans que terminaram antes do erro, num trace descartado, já foram jogados fora: preservar o trace inteiro não é possível no SDK.
Envio, lotes e encerramento
- Lote a cada 2 s ou 200 spans, fora do caminho da resposta; até 1000 spans e 1,5 MiB por envio, com gzip.
- Rede, timeout, 408,
429e 5xx: o lote volta para a fila e espera um backoff de 1 s a 60 s, ou oRetry-Afterda resposta.413divide o lote. Outro 4xx (chave errada, por exemplo) descarta o lote. - Reenviar não duplica: a API ignora span repetido (mesmo trace e span).
- A fila guarda até 5000 spans e 32 MiB; acima disso descarta os mais antigos. Falhas aparecem uma vez no console até um envio dar certo.
- Ao sair:
beforeExitenvia a fila (scripts e jobs). EmSIGTERM/SIGINT, o SDK envia e reemite o sinal se a aplicação não tiver handler próprio; se tiver, chameshutdown()nele.
process.once('SIGTERM', async () => {
await tracker.shutdown(); // envia a fila em até 10 s (shutdownTimeoutMs)
process.exit(0);
});Logs
O mesmo pacote envia logs para POST /v1/logs, ligados ao trace quando a linha nasce dentro de uma requisição. O transporte também precisa de endpoint.
import pino from 'pino';
import { pinoTransport, traceMixin } from 'fg-tracker-sdk';
import { tracker } from './tracker.js';
export const logger = pino({
mixin: traceMixin(tracker), // traceId/spanId em cada linha
transport: pinoTransport({
apiKey: process.env.FG_TRACKER_KEY,
endpoint: process.env.FG_TRACKER_URL,
service: 'checkout-api',
env: process.env.FG_TRACKER_ENV,
}),
});winston: new FgTrackerTransport({ apiKey, endpoint, service, tracker }) de fg-tracker-sdk/winston. Nest: nestLogger({ apiKey, endpoint, service, tracker }). Os logs ficam 7 dias.
Jobs e releases
Monitor de job (check-in)
await tracker.checkIn('backup-diario', () => rodarBackup(), {
cron: '0 3 * * *',
timezone: 'America/Sao_Paulo',
maxRuntimeSeconds: 1800,
});O primeiro check-in cria o monitor; se o job não rodar no horário ou passar do tempo máximo, o painel marca e alerta. Sem SDK, use o ping por curl.
Registrar a release no deploy
FG_TRACKER_KEY=$FG_TRACKER_KEY FG_TRACKER_URL=https://tracker-api.fortground.com.br \
npx -p fg-tracker-sdk fg-tracker-release --service checkout-api --env production --previous-sha "$PREVIOUS_SHA"O CLI lê o git log entre os dois SHAs e envia commits e arquivos alterados; o painel usa isso para apontar commits suspeitos em cada issue. Ele lê FG_TRACKER_KEY e FG_TRACKER_URL do ambiente (sem a URL, usa localhost:4050).