Instalação do agente de trace – Node.js

Plugin Node.js que integra OpenTelemetry com autoinstrumentação de traces e um logger estruturado que adiciona traceId e spanId para correlação dos logs com traces no Grafana Tempo.

Funciona com qualquer app Node.js e já tem middlewares para Express, Apollo GraphQL e suporte para filtro global de erros no NestJS.

✅ Pré-requisitos #
  • Node.js >= 16
  • Aplicações Node.js (Express, NestJS, Apollo GraphQL ou outras)
  • Ambiente configurado com variáveis de ambiente para OpenTelemetry
🛠️ Etapas de instalação #
npm install kmind-apm
⚙️ Configuração das variáveis de ambiente #
VariávelDescrição
OTEL_ENABLE_TRACEHabilita ou desabilita o tracing "true" ou "false"
OTEL_SERVICE_NAMENome do serviço para o trace (ex: api-users)
OTEL_CLUSTER_NAMENome do cluster (ex: producao-eks)
OTEL_CONTAINER_NAMENome do container (ex: api-graphql)
OTEL_EXPORTER_OTLP_ENDPOINTEndpoint OTLP HTTP para enviar traces
https://trace.kmind.com.br/v1/traces
OTEL_TENANT_IDIdentificador do tenant
É um número único de identificação fornecido pela equipe da Kmind.
Caso ainda não tenha recebido esse valor, entre em contato com nosso suporte.
ENABLE_TRACE_DEBUGAtiva logs detalhados de debug do tracing
"true" ou "false" (use apenas em desenvolvimento)
DEBUG_HTTP_REQUESTSLoga requisições HTTP (middleware Express)
"true" ou "false"
⚠️ Inicialização (IMPORTANTE) #

O OpenTelemetry DEVE ser inicializado ANTES de qualquer outro import. Existem duas formas de fazer isso:

Método 1: Flag –require (Recomendado)

Esta é a forma mais profissional e usada por APMs como Datadog e New Relic.

No seu package.json:

{
  "scripts": {
    "start": "node --require kmind-apm/init dist/main.js",
    "dev": "tsx --require kmind-apm/init src/main.ts"
  }
}

Com este método, seu código não precisa de imports especiais:

// main.ts - nenhum import especial necessário
import express from 'express';
import { logger } from 'kmind-apm';

const app = express();
// ... resto do código
Método 2: Import direto

Adicione como primeira linha do seu arquivo principal:

// main.ts
import 'kmind-apm/init'; // PRIMEIRA LINHA!

// Agora pode importar o resto
import express from 'express';
import { logger } from 'kmind-apm';
Como usar #

Em qualquer aplicação Node.js

import 'kmind-apm/init';

import { logger } from 'kmind-apm';

// Use o logger para logs correlacionados
logger.info('Aplicação iniciada', { port: 3000 });

Com Express.js

import 'kmind-apm/init';

import express from 'express';
import {
  logger,
  requestLogger,
  errorHandler,
  notFoundHandler,
} from 'kmind-apm';

const app = express();

// Middleware de logging (opcional)
app.use(requestLogger);

// Suas rotas aqui
app.get('/', (req, res) => {
  logger.info('Rota raiz acessada');
  res.send('Olá!');
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

// Handlers de erro (sempre no final)
app.use(notFoundHandler);
app.use(errorHandler);

app.listen(3000, () => {
  logger.info('Servidor iniciado', { port: 3000 });
});

Com Apollo Server – usando plugins

1 – No arquivo principal (ex: main.ts ou index.ts), crie uma função para o plugin Apollo:

import 'kmind-apm/init';

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import {
  logger,
  errorHandler,
  notFoundHandler,
  graphqlErrorHandler,
  graphqlSuccessLogger
} from 'kmind-apm';

// Plugin Apollo com kmind-apm
export function apolloLoggingPlugin() {
  return {
    async requestDidStart(requestContext: any) {
      const start = Date.now();

      return {
        async didEncounterErrors(ctx: any) {
          const duration = Date.now() - start;
          graphqlErrorHandler(ctx, duration);
        },

        async willSendResponse(ctx: any) {
          const duration = Date.now() - start;
          if (!ctx.errors || ctx.errors.length === 0) {
            graphqlSuccessLogger(ctx, duration);
          }
        }
      };
    }
  };
}

2 – Na instância do ApolloServer, adicione o plugin criado no atributo plugins:

const app = express();

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: true,
  plugins: [
    apolloLoggingPlugin(),
    // ... outros plugins
  ],
});

await server.start();

app.use('/graphql', expressMiddleware(server, {
  context: async ({ req }) => ({
    // ... seu contexto
  })
}));

// Handlers de erro
app.use(notFoundHandler);
app.use(errorHandler);

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
  logger.info('GraphQL server ready', { port: PORT, path: '/graphql' });
});

Com NestJS – usando GlobalExceptionFilter

1 – Crie o filtro global de exceção:

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, NotFoundException } from '@nestjs/common';
import { Response, Request } from 'express';
import { errorHandler, notFoundHandler } from 'kmind-apm';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const res = ctx.getResponse<Response>();
    const req = ctx.getRequest<Request>();

    let status = 500;
    let message: any = 'Internal Server Error';

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      message = exception.getResponse();
    }

    if (exception instanceof NotFoundException) {
      return notFoundHandler(req, res, () => {});
    }

    errorHandler(exception, req, res, () => {
      res.status(status).json(
        typeof message === 'string'
          ? { statusCode: status, message }
          : message,
      );
    });
  }
}

2 – No seu main.ts, faça:

import 'kmind-apm/init';

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger, requestLogger } from 'kmind-apm';
import { GlobalExceptionFilter } from './filters/global-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Log de requests (apenas se DEBUG_HTTP_REQUESTS=true)
  app.use(requestLogger);
  app.useGlobalFilters(new GlobalExceptionFilter());

  await app.listen(process.env.PORT ?? 3000);
  logger.info('NestJS application started', { port: process.env.PORT ?? 3000 });
}
bootstrap();
📊 Uso do Logger #

O logger do kmind-apm automaticamente inclui traceId e spanId em todos os logs:

import { logger } from 'kmind-apm';

// Log de informação
logger.info('User created successfully', {
  userId: '123',
  email: 'user@example.com'
});

// Log de erro
logger.error('Failed to create user', {
  error: err.message,
  userId: '123'
});

Saída (JSON):

{
  "timestamp": "2025-12-20T15:30:00.000Z",
  "level": "info",
  "message": "User created successfully",
  "traceId": "a1b2c3d4e5f6g7h8",
  "spanId": "1a2b3c4d",
  "userId": "123",
  "email": "user@example.com"
}
🔗 Logs e correlação #

Todos os logs enviados pelo logger do plugin já terão os campos:

  • traceId – Identificador único do trace
  • spanId – Identificador único do span
  • timestamp – Data e hora do log
  • level – Nível do log (info, error)

Assim, no Grafana Tempo e Loki, você pode correlacionar facilmente logs e traces.

Buscando logs por trace no Grafana Loki:

{service="meu-servico"} | json | traceId="a1b2c3d4e5f6g7h8"
🛡️ Captura de erros globais #

O plugin automaticamente registra:

  • uncaughtException – Exceções não capturadas
  • unhandledRejection – Promises rejeitadas não tratadas

Com logs estruturados incluindo traceId e spanId quando disponíveis.

🐛 Debug e Troubleshooting #

Ativando logs de debug

Para debugar problemas de tracing, ative a variável de ambiente:

ENABLE_TRACE_DEBUG=true npm start

Você verá logs detalhados como:

[TRACE_DEBUG 2025-12-20T15:30:00.000Z] Starting tracing initialization | Data: {
  "serviceName": "meu-servico",
  "otlpEndpoint": "https://trace.kmind.com.br/v1/traces",
  "tenantId": "5"
}
[TRACE_DEBUG 2025-12-20T15:30:01.000Z] Tracing SDK started successfully
[TRACE_DEBUG 2025-12-20T15:30:05.000Z] Attempting to export spans | Data: {
  "spanCount": 3
}
[TRACE_DEBUG 2025-12-20T15:30:05.100Z] Spans exported successfully

Problemas Comuns

Problema: Traces não aparecem no Grafana Tempo

Solução:

  1. Verifique se import 'kmind-apm/init' é a primeira linha do arquivo
  2. Ou use --require kmind-apm/init no script de inicialização
  3. Ative debug para verificar: ENABLE_TRACE_DEBUG=true
  4. Confirme que o endpoint está correto e acessível

Problema: Logs sem traceId/spanId

Explicação: O OpenTelemetry só cria spans automaticamente para requisições HTTP, operações GraphQL, chamadas de banco de dados e outras operações instrumentadas. Logs em código que roda fora desses contextos (inicialização, cronjobs) não terão traceId/spanId.

📚 Middlewares Disponíveis #
  • errorHandler – Captura erros não tratados e loga com contexto de trace
  • notFoundHandler – Loga requisições para rotas não encontradas (404)
  • requestLogger – Loga todas as requisições HTTP (útil para debug)
  • graphqlErrorHandler – Processa erros do Apollo GraphQL
  • graphqlSuccessLogger – Loga operações GraphQL bem-sucedidas
🔍 Exemplo Completo #
// main.ts
import 'kmind-apm/init';

import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import {
  logger,
  errorHandler,
  notFoundHandler,
  graphqlErrorHandler,
  graphqlSuccessLogger
} from 'kmind-apm';

const app = express();
app.use(express.json());

// Plugin Apollo
function apolloLoggingPlugin() {
  return {
    async requestDidStart() {
      const start = Date.now();
      return {
        async didEncounterErrors(ctx: any) {
          graphqlErrorHandler(ctx, Date.now() - start);
        },
        async willSendResponse(ctx: any) {
          if (!ctx.errors?.length) {
            graphqlSuccessLogger(ctx, Date.now() - start);
          }
        }
      };
    }
  };
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [apolloLoggingPlugin()],
});

await server.start();

app.use('/graphql', expressMiddleware(server));

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

app.use(notFoundHandler);
app.use(errorHandler);

const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
  logger.info('Server started', { port: PORT });
});
💬 Suporte #

Para dúvidas ou problemas, entre em contato com a equipe Kmind.