OpenTelemetry: Observabilidade Unificada para PME em 2026

Introdução: Metrics, Logs e Traces

O OpenTelemetry (OTel) é o padrão CNCF para observabilidade, unificando métricas, logs e traces num único framework open-source. Em 2026, é a solução de facto para pequenas e médias empresas (PME) que precisam de visibilidade sobre sistemas distribuídos sem ficar refém de vendors comerciais caros como Datadog ou New Relic.

Os três pilares da observabilidade são:

Métricas — dados numéricos agregados ao longo do tempo (CPU, memória, latency de requests, taxa de erros). Respondem à pergunta “o que está a acontecer?”. São leves e baratas de armazenar, ideais para dashboards e alertas em tempo real.

Logs — eventos discretos com timestamp (uma linha de log, uma mensagem de erro, uma entrada de auditoria). Respondem à pergunta “porque é que aconteceu?”. Contêm o contexto detalhado que métricas e traces não capturam.

Traces — o caminho completo de um request através de múltiplos serviços, dividido em spans. Respondem à pergunta “onde é que falhou?”. Permitem identificar o bottleneck exacto numa cadeia de microserviços, desde a entrada até à base de dados.

ℹ Porquê unificar os três pilares?

Antes do OTel, cada pilar usava uma ferramenta separada (Prometheus para métricas, ELK para logs, Jaeger para traces) com instrumentação manual e incompatível entre si. O OpenTelemetry unifica a recolha, processamento e exportação num só pipeline, reduzindo a complexidade operacional para PME que não têm equipas dedicadas de SRE.

Collector e Arquitectura

O OpenTelemetry Collector é o componente central da arquitectura. Recebe dados de múltiplas fontes (apps, infraestrutura, bibliotecas), processa-os (filtragem, transformação, batching) e exporta para um ou mais backends. É um binário único escrito em Go, sem dependências externas.

A arquitectura típica para uma PME tem três camadas:

1. Instrumentação — SDKs do OTel nas apps enviam dados via OTLP (OpenTelemetry Protocol) para o Collector. Suporta auto-instrumentação em Python, Node.js, Java, .NET e Go.

2. Collector — processo intermédio que recebe, processa e encaminha. Pode correr como sidecar em Kubernetes, container standalone em Docker, ou serviço em VM. Configurado via YAML.

3. Backends — Prometheus (métricas), Loki (logs), Tempo ou Jaeger (traces). Todos open-source e auto-alojável.

Configuração mínima do Collector para uma PME com Docker:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1000
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 25
  resource:
    attributes:
      - key: deployment.environment
        value: produção
        action: upsert

exporters:
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
  loki:
    endpoint: http://loki:3100/loki/api/v1/push
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/tempo]

O memory_limiter é obrigatório — sem ele, picos de tráfego podem fazer o Collector consumir toda a memória disponível e morrer com OOM kill. O batch agrupa dados antes de enviar, reduzindo o número de requests HTTP aos backends.

Para correr o Collector em Docker:

docker run -d --name otel-collector \
  -p 4317:4317 -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otelcol/config.yaml \
  otel/opentelemetry-collector-contrib:latest \
  --config=/etc/otelcol/config.yaml

💡 Dica

Use a imagem otel/opentelemetry-collector-contrib e não a core. A versão contrib inclui todos os exporters (Loki, Prometheus remote write, etc.) que a core não tem.

Exportar para Prometheus e Loki

O Collector pode exportar métricas de duas formas para Prometheus: via prometheus exporter (Collector expõe um endpoint scrape) ou via prometheusremotewrite (Collector envia para o Prometheus). Para PME, o remote write é mais simples porque não requer configurar scrape configs no Prometheus.

Para activar remote write no Prometheus, adicionar ao prometheus.yml:

# prometheus.yml
remote_write:
  - url: http://prometheus:9090/api/v1/write

# Garantir que o feature flag está activo
# Iniciar Prometheus com:
# --enable-feature=remote-write-receiver

scrape_configs:
  - job_name: otel-collector
    static_configs:
      - targets: [otel-collector:8888]
    scrape_interval: 15s

Para o Loki, o exporter envia logs directamente via HTTP push. Não é preciso configurar nada no Loki além do endpoint padrão. O Collector envia os logs com os atributos do OTel (service.name, trace_id, span_id) como labels do Loki, permitindo correlacionar logs com traces.

Um docker-compose típico para uma PME com toda a stack:

# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    ports: ["4317:4317", "4318:4318"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otelcol/config.yaml

  prometheus:
    image: prom/prometheus:latest
    ports: ["9090:9090"]
    command: --enable-feature=remote-write-receiver
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  loki:
    image: grafana/loki:latest
    ports: ["3100:3100"]

  tempo:
    image: grafana/tempo:latest
    ports: ["4317"]

  grafana:
    image: grafana/grafana:latest
    ports: ["3000:3000"]
    environment:
      GF_SECURITY_ADMIN_PASSWORD: mudar-esta-palavra-passe

O Grafana serve como painel único para visualizar métricas (Prometheus), logs (Loki) e traces (Tempo). A correlação entre os três pilares é automática porque o OTel propaga o trace_id entre métricas, logs e traces.

✓ Toda a stack em menos de 100MB de YAML

Com 4 containers (Collector, Prometheus, Loki, Grafana) uma PME tem observabilidade completa e auto-alojável. O custo de alojamento não excede 20€/mês num VPS com 2GB RAM.

Instrumentação de Apps

O OpenTelemetry oferece dois nˆveis de instrumentação: automática (zero code changes) e manual (spans e métricas customizadas). Para PME, começar com auto-instrumentação e ir adicionando instrumentação manual conforme necessário.

Python — auto-instrumentação via pacote opentelemetry-instrument:

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

# Correr a app com instrumentação automática
opentelemetry-instrument \
  --service_name minha-api \
  --exporter otlp \
  --endpoint http://otel-collector:4317 \
  python app.py

Isto instrumenta automaticamente Flask, Django, FastAPI, requests, SQLAlchemy e Redis, gerando traces e mítricas sem alterar uma linha de código.

Node.js — auto-instrumentação via @opentelemetry/auto-instrumentations:

npm install @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-grpc

# tracing.js - carregar ANTES da app
const { NodeSDK } = require("@opentelemetry/auto-instrumentations-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-grpc");

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: "http://otel-collector:4317",
  }),
  serviceName: "minha-api-node",
});
sdk.start();

Carregar com node --require ./tracing.js app.js. O ficheiro tracing.js tem de ser carregado antes de qualquer outra importação para que os hooks de instrumentação funcionem.

Instrumentação manual para adicionar spans customizados em Python:

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

def processar_pagamento(pedido_id):
    with tracer.start_as_current_span("processar_pagamento") as span:
        span.set_attribute("pedido.id", pedido_id)
        span.set_attribute("pagamento.método", "cartão")

        # lógica de negócio
        resultado = gateway_cobrar(pedido_id)

        span.set_attribute("pagamento.status", resultado.status)
        span.record_exception(resultado.erro) if resultado.erro else None
        return resultado

Os atributos customizados aparecem no trace no Grafana Tempo, permitindo filtrar e procurar pagamentos por ID, método ou status. Isto é a vantagem real do OTel: contexto estruturado que não existe em logs simples.

⚠ Atenção ao overhead

A instrumentação manual com muitos atributos por span pode adicionar 5-15% de overhead. Para PME com tráfego moderado, não é problemático. Para alto tráfego, usar batch processor e sampling (ex: traceidratio com ratio 0.1) para reduzir volume.

Erros Comuns

A tabela abaixo resume os erros mais frequentes na implementação do OpenTelemetry em PME, com causa e solução:

Problema Causa Solução
Sem traces no backend Endpoint errado (4317 gRPC vs 4318 HTTP) Confirmar protocolo: gRPC usa 4317, HTTP usa 4318
Collector morre com OOM Falta memory_limiter processor Adicionar memory_limiter no início de cada pipeline
Logs sem trace_id no Loki Logs não instrumentados com OTel context Usar OTel logging SDK ou inject trace_id manualmente
Métricas duplicadas no Prometheus Scrape + remote write activos em simultâneo Usar só remote write OU só scrape, nunca ambos
Latência alta na app após OTel Export síncrono a bloquear requests Configurar export assíncrono com batch processor
Spans não se propagam entre serviços Falta propagação de contexto (W3C TraceContext) Garantir que SDK injecta traceparent header
Collector não arranca Imagem core em vez de contrib (sem exporters) Usar opentelemetry-collector-contrib:latest
Volume de traces excessivo Sampling a 100% em produção Configurar traceidratio com 0.05-0.1 em produção

O erro mais comum em PME é o primeiro: usar o endpoint errado. O OTLP tem dois portos — 4317 para gRPC e 4318 para HTTP. Misturar os portos resulta em traces que nunca chegam ao Collector, sem qualquer erro visível. Verificar sempre com otelcol debug logs (--log-level=debug).