Pular para o conteúdo
FG Tracker
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

terminal
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, use await 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
// 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ções principais do createTracker
OpçãoPadrãoO que faz
apiKey—Chave de servidor fgsk_…. Vazia, o tracker desliga e avisa no console.
endpointhttp://localhost:4050URL base da API, sem /v1. Em produção, passe sempre.
serviceName—Nome do serviço em todos os spans.
environmentFG_TRACKER_ENV, APP_ENV ou NODE_ENVAmbiente dos spans. false desliga.
releaseFG_TRACKER_RELEASE ou o SHA do CIVersão em execução, usada para comparar releases.
tracesSampleRate1Fração de traces enviados.
instrumentGlobalFetchfalseLiga spans para o fetch do Node.
propagateTraceTotrueDestinos que recebem o header traceparent.
captureGlobalErrorsfalseuncaughtException e unhandledRejection viram spans com erro.
scrubFieldspassword, 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.
debugfalseMostra cada envio no console.
enabledtruefalse 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
// 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/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

db.js
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.query dentro de uma requisição vira um span db.query com 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 span pg connect (com a espera por conexão), as queries do client devolvido viram db.query, e BEGIN … COMMIT/ROLLBACK vira pg 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 spans http.client sem configuração.
  • O fetch nativo do Node não é coberto por padrão: ligue instrumentGlobalFetch: true ou use tracker.wrapFetch().
  • Por padrão, o header traceparent vai para qualquer destino. Para não mandar ids de trace a terceiros, restrinja aos seus domínios com propagateTraceTo: um texto casa o host exato ou qualquer subdomínio dele ('suaempresa.com.br' cobre api.suaempresa.com.br), um texto que começa com https:// é prefixo da URL, e uma RegExp é testada contra a URL inteira, com caminho (não ancore em $ depois do domínio).
src/tracker.js
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:

fila.js
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: SendMessage e SendMessageBatch ganham o span SQS publish <fila> e o atributo de mensagem traceparent, 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, informe sqsMaxMessageBytes no instrumentAws para propagar em mensagens acima de 1 KiB.
  • Consumidor: trackMessage(fila, record, fn) aceita o record do evento da Lambda ou a mensagem do ReceiveMessage e mede a latência desde o envio e as tentativas. trackMessage(fila, fn), sem o record, mede só localmente.
  • EventBridge: PutEvents ganha traceparent dentro do Detail (quando ele é um objeto JSON). Consumidores que validam um schema estrito passam a ver essa chave; nesse client, use instrumentAws(client, { propagation: false }).
  • wrapLambda continua o trace de eventos de origem única (um record SQS, EventBridge com detail.traceparent, HTTP com traceparent); um lote de mensagens fica num trace próprio, com links para cada produtor.

Spans manuais

exemplo
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 ms

span 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.js
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 sobre Nome: mensagem) mantém o span mas não abre issue.

Amostragem

src/tracker.js
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 traceparent segue 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, 429 e 5xx: o lote volta para a fila e espera um backoff de 1 s a 60 s, ou o Retry-After da resposta. 413 divide 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: beforeExit envia a fila (scripts e jobs). Em SIGTERM/SIGINT, o SDK envia e reemite o sinal se a aplicação não tiver handler próprio; se tiver, chame shutdown() nele.
encerramento
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.

logger.js (pino)
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)

job.js
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

CI
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).