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ável | Descrição |
OTEL_ENABLE_TRACE | Habilita ou desabilita o tracing "true" ou "false" |
OTEL_SERVICE_NAME | Nome do serviço para o trace (ex: api-users) |
OTEL_CLUSTER_NAME | Nome do cluster (ex: producao-eks) |
OTEL_CONTAINER_NAME | Nome do container (ex: api-graphql) |
OTEL_EXPORTER_OTLP_ENDPOINT | Endpoint OTLP HTTP para enviar traceshttps://trace.kmind.com.br/v1/traces |
OTEL_TENANT_ID | Identificador do tenantÉ um número único de identificação fornecido pela equipe da Kmind. |
ENABLE_TRACE_DEBUG | Ativa logs detalhados de debug do tracing"true" ou "false" (use apenas em desenvolvimento) |
DEBUG_HTTP_REQUESTS | Loga 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ódigoMé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 tracespanId– Identificador único do spantimestamp– Data e hora do loglevel– 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 capturadasunhandledRejection– 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 startVocê 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 successfullyProblemas Comuns
Problema: Traces não aparecem no Grafana Tempo
Solução:
- Verifique se
import 'kmind-apm/init'é a primeira linha do arquivo - Ou use
--require kmind-apm/initno script de inicialização - Ative debug para verificar:
ENABLE_TRACE_DEBUG=true - 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 tracenotFoundHandler– Loga requisições para rotas não encontradas (404)requestLogger– Loga todas as requisições HTTP (útil para debug)graphqlErrorHandler– Processa erros do Apollo GraphQLgraphqlSuccessLogger– 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.
