OpenTelemetry em Node.js: tracing distribuído

    Quando uma requisição leva 2 segundos, logs e métricas dizem "demorou" — OpenTelemetry tracing mostra exatamente onde: 50ms na autenticação, 1,8s em uma query PostgreSQL lenta, 150ms na chamada à API de pagamento. Traces distribuídos conectam toda a cadeia de chamadas de uma requisição, do browser ao banco de dados.

    Instrumentação automática do Node.js

    Configurar OpenTelemetry com instrumentação zero-code:

    typescript
    # npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
    # npm install @opentelemetry/exporter-otlp-http
    
    // instrumentation.ts — carregar ANTES do código da aplicação
    import { NodeSDK } from '@opentelemetry/sdk-node'
    import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
    import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
    import { Resource } from '@opentelemetry/resources'
    import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
    
    const sdk = new NodeSDK({
      resource: new Resource({
        [SEMRESATTRS_SERVICE_NAME]: 'minha-api',
      }),
      traceExporter: new OTLPTraceExporter({
        url: 'http://localhost:4318/v1/traces',  // Jaeger ou Grafana Tempo
      }),
      instrumentations: [
        getNodeAutoInstrumentations({
          // Instrumentação automática de:
          // HTTP, Express, PostgreSQL, Redis, gRPC, fetch, etc.
          '@opentelemetry/instrumentation-pg': { enhancedDatabaseReporting: true },
          '@opentelemetry/instrumentation-http': { ignoreIncomingRequestHook: (req) => req.url === '/metrics' },
        }),
      ],
    })
    
    sdk.start()
    
    // package.json — carregar antes do código:
    // "start": "node -r ./dist/instrumentation.js dist/index.js"

    Criar spans customizados

    Rastrear operações específicas da sua aplicação:

    typescript
    import { trace, SpanStatusCode, context } from '@opentelemetry/api'
    
    const tracer = trace.getTracer('minha-api')
    
    async function processarPedido(id: string) {
      // Criar span pai:
      return tracer.startActiveSpan('processarPedido', async (span) => {
        span.setAttribute('pedido.id', id)
        span.setAttribute('pedido.tipo', 'checkout')
    
        try {
          // Spans filhos automáticos (pg, redis instrumentados automaticamente)
          const usuario = await db.query('SELECT * FROM usuarios WHERE id = $1', [id])
    
          // Span filho manual para lógica de negócio:
          const desconto = await tracer.startActiveSpan('calcularDesconto', async (s) => {
            s.setAttribute('usuario.plano', usuario.plano)
            const d = await calcularDesconto(usuario)
            s.setAttribute('desconto.valor', d)
            s.end()
            return d
          })
    
          span.setAttribute('pedido.desconto', desconto)
          span.setStatus({ code: SpanStatusCode.OK })
          return { usuario, desconto }
        } catch (err) {
          span.recordException(err as Error)
          span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message })
          throw err
        } finally {
          span.end()
        }
      })
    }

    Instalar Jaeger para visualizar traces

    Backend de tracing self-hosted com Jaeger:

    yaml
    # docker-compose.yml — adicionar Jaeger:
    services:
      jaeger:
        image: jaegertracing/all-in-one:latest
        restart: always
        ports:
          - "127.0.0.1:16686:16686"  # UI web
          - "127.0.0.1:4317:4317"    # OTLP gRPC
          - "127.0.0.1:4318:4318"    # OTLP HTTP
        environment:
          COLLECTOR_OTLP_ENABLED: "true"
          SPAN_STORAGE_TYPE: badger    # storage em arquivo (simples)
        volumes:
          - jaeger_data:/badger
    
    volumes:
      jaeger_data:
    
    # Expor Jaeger UI via Nginx (acesso restrito):
    # /etc/nginx/conf.d/jaeger.conf:
    server {
        listen 443 ssl http2;
        server_name tracing.seudominio.com.br;
        allow 177.10.0.1;
        deny all;
        location / {
            proxy_pass http://127.0.0.1:16686;
            proxy_set_header Host $host;
        }
    }

    Correlacionar traces com logs

    Adicionar trace ID e span ID nos logs para correlação:

    typescript
    // Adicionar trace ID nos logs do Pino para correlação com Jaeger:
    import pino from 'pino'
    import { trace, context } from '@opentelemetry/api'
    
    const logger = pino()
    
    // Middleware que injeta trace ID em cada requisição:
    app.use((req, res, next) => {
      const span = trace.getActiveSpan()
      const spanContext = span?.spanContext()
    
      // Criar child logger com trace context:
      req.log = logger.child({
        traceId: spanContext?.traceId,
        spanId: spanContext?.spanId,
      })
      next()
    })
    
    // Usar req.log nas rotas:
    app.post('/pedidos', async (req, res) => {
      req.log.info('Iniciando criação de pedido')
      try {
        const pedido = await criarPedido(req.body)
        req.log.info({ pedidoId: pedido.id }, 'Pedido criado com sucesso')
        res.json(pedido)
      } catch (err) {
        req.log.error({ err }, 'Erro ao criar pedido')
        throw err
      }
    })
    
    // No Grafana: buscar pelo traceId nos logs do Loki
    // e saltar direto para o trace no Jaeger/Tempo

    Grafana Tempo como alternativa ao Jaeger

    Backend de tracing mais integrado ao ecossistema Grafana:

    yaml
    # docker-compose.yml — Grafana Tempo:
    services:
      tempo:
        image: grafana/tempo:latest
        restart: always
        ports:
          - "127.0.0.1:4317:4317"   # OTLP gRPC
          - "127.0.0.1:4318:4318"   # OTLP HTTP
          - "127.0.0.1:3200:3200"   # Tempo API (para Grafana)
        command: -config.file=/etc/tempo/config.yaml
        volumes:
          - tempo_data:/var/tempo
          - ./tempo-config.yaml:/etc/tempo/config.yaml
    
    # tempo-config.yaml:
    server:
      http_listen_port: 3200
    
    distributor:
      receivers:
        otlp:
          protocols:
            http:
            grpc:
    
    storage:
      trace:
        backend: local
        local:
          path: /var/tempo/traces
        wal:
          path: /var/tempo/wal
    
    # No Grafana — adicionar Tempo como data source:
    # Configuration → Data Sources → Tempo
    # URL: http://tempo:3200
    
    # Correlação automática Loki → Tempo no Grafana:
    # No data source Loki: adicionar derived field "traceID"
    # Regex: "traceId":"([a-f0-9]{32})"
    # Internal link → Tempo data source

    $ runstack deploy --plan starter

    Não quer configurar manualmente?

    Não quer configurar manualmente? Implante o VPS para APIs em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.

    Perguntas frequentes