<h2>O que é OpenTelemetry e por que você precisa dele</h2>
<p>OpenTelemetry é um framework open-source mantido pela Cloud Native Computing Foundation (CNCF) que oferece um padrão único para coletar, processar e exportar dados de observabilidade de suas aplicações. Antes dele, você precisava integrar múltiplas bibliotecas diferentes para rastrear métricas, logs e traces — cada uma com sua própria API, convenção e configuração. OpenTelemetry unifica tudo isso em um único SDK agnóstico a fornecedores.</p>
<p>A essência do OpenTelemetry é ser <strong>vendor-agnostic</strong>. Você instrumenta sua aplicação uma única vez e escolhe depois para onde enviar os dados: pode ser Prometheus para métricas, ELK para logs, Jaeger para traces, ou qualquer outro backend. Isso reduz acoplamento e oferece flexibilidade real. Além disso, instrumentar desde cedo em um projeto é muito mais barato do que tentar adicionar observabilidade depois, quando a aplicação já está em produção.</p>
<h2>Os três pilares: Traces, Métricas e Logs</h2>
<h3>O que é um Trace?</h3>
<p>Um trace é o registro completo de uma requisição viajando através de múltiplos serviços em uma arquitetura distribuída. Ele é composto por <strong>spans</strong>, que são unidades de trabalho nomeadas dentro dessa jornada. Quando você faz uma chamada HTTP para um serviço A, que depois chama um banco de dados, e depois chama um serviço B, cada uma dessas operações é um span dentro de um único trace. O valor real de traces aparece em sistemas onde você tem dezenas ou centenas de serviços — sem eles, é impossível entender onde uma requisição falhou ou ficou lenta.</p>
<pre><code class="language-python"># Exemplo básico de trace com Python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
Configurar exportador Jaeger
jaeger_exporter = JaegerExporter(
agent_host_name="localhost",
agent_port=6831,
)
Configurar provider
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(jaeger_exporter)
)
Obter tracer
tracer = trace.get_tracer(__name__)
Criar um span
with tracer.start_as_current_span("processar_pagamento") as span:
span.set_attribute("usuario_id", 123)
span.set_attribute("valor", 99.99)
seu código aqui
print("Processando pagamento...")</code></pre>
<h3>O que são Métricas?</h3>
<p>Métricas são medições numéricas agregadas colhidas em intervalos regulares. Diferente de traces que olham requisição por requisição, métricas respondem perguntas como "qual é a taxa de erro agora?" ou "qual é a latência p99 nos últimos 5 minutos?". Existem vários tipos: counters (incrementam), gauges (valores instantâneos), histograms (distribuições de valores) e rate histograms. Métricas são leves e escaláveis — você pode ter bilhões de pontos de métrica em produção sem impacto significativo de performance.</p>
<pre><code class="language-python"># Exemplo de métricas com OpenTelemetry
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.prometheus import PrometheusMetricReader
Configurar Prometheus como exportador
prometheus_reader = PrometheusMetricReader()
provider = MeterProvider(metric_readers=[prometheus_reader])
metrics.set_meter_provider(provider)
Obter meter
meter = metrics.get_meter(__name__)
Criar um counter (incrementa)
request_counter = meter.create_counter(
name="requisicoes_total",
description="Total de requisições processadas",
unit="1",
)
Criar um histogram (distribui valores)
latencia_histogram = meter.create_histogram(
name="latencia_ms",
description="Latência das requisições em milissegundos",
unit="ms",
)
Usar as métricas
request_counter.add(1, {"metodo": "GET", "rota": "/api/usuarios"})
latencia_histogram.record(45, {"rota": "/api/usuarios"})</code></pre>
<h3>O que são Logs?</h3>
<p>Logs são registros estruturados de eventos discretos que aconteceram na aplicação. Com OpenTelemetry, você pode estruturar seus logs em formato padronizado e correlacioná-los automaticamente com traces — cada log carrega o trace_id e span_id, permitindo navegar entre logs e traces sem esforço. Logs estruturados são tão importantes quanto o conteúdo em si; enviar <code>{"nivel": "ERROR", "mensagem": "conexão falhou", "servico": "database", "erro": "timeout"}</code> é muito melhor que uma string texto puro.</p>
<pre><code class="language-python"># Exemplo de logs estruturados com OpenTelemetry
from opentelemetry import logs
from opentelemetry.sdk.logs import LoggerProvider
from opentelemetry.sdk.logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc.log_exporter import OTLPLogExporter
import logging
Configurar OTLP como exportador
otlp_exporter = OTLPLogExporter(insecure=True)
logger_provider = LoggerProvider()
logger_provider.add_log_record_processor(BatchLogRecordProcessor(otlp_exporter))
logs.set_logger_provider(logger_provider)
Integrar com logging padrão do Python
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Logs estruturados
logger.info(
"Usuario criado com sucesso",
extra={
"usuario_id": 456,
"email": "joao@example.com",
"origem_ip": "192.168.1.1"
}
)</code></pre>
<h2>Instrumentação: Automática vs Manual</h2>
<h3>Instrumentação Automática</h3>
<p>OpenTelemetry oferece instrumentações automáticas (chamadas de instrumentation libraries) para frameworks populares como Django, FastAPI, Flask, requests HTTP, banco de dados (SQLAlchemy, psycopg2), Redis e muito mais. A ideia é simples: você instala o pacote da instrumentação e ela se conecta aos hooks internos do framework, capturando traces e métricas sem você escrever uma linha de código. Isso é poderoso porque reduz o boilerplate e garante boas práticas desde o início.</p>
<pre><code class="language-python"># Instalação de instrumentações automáticas
pip install opentelemetry-instrumentation-flask
pip install opentelemetry-instrumentation-sqlalchemy
pip install opentelemetry-instrumentation-requests
from flask import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
app = Flask(__name__)
Configurar exportador OTLP (OpenTelemetry Protocol)
otlp_exporter = OTLPSpanExporter(insecure=True)
trace_provider = TracerProvider()
trace_provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
Instrumentar automaticamente
FlaskInstrumentor().instrument_app(app)
SQLAlchemyInstrumentor().instrument()
@app.route('/api/usuarios/<int:user_id>')
def get_user(user_id):
Traces de HTTP e DB queries são capturados automaticamente
return {"id": user_id, "nome": "João Silva"}
if __name__ == '__main__':
app.run()</code></pre>
<h3>Instrumentação Manual</h3>
<p>Para lógica customizada específica do seu negócio, você precisa de instrumentação manual. Por exemplo, se você tem uma função que calcula recomendações de produtos ou processa um arquivo pesado, você quer criar spans para isso. A instrumentação manual é simples: peça um tracer ao provider e crie spans nomeados semanticamente. Não é verboso e oferece controle fino.</p>
<pre><code class="language-python"># Instrumentação manual para lógica de negócio
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def processar_pedido(pedido_id: int, itens: list) -> dict:
"""Função customizada que precisa de observabilidade"""
with tracer.start_as_current_span("processar_pedido") as span:
span.set_attribute("pedido_id", pedido_id)
span.set_attribute("quantidade_itens", len(itens))
Validar itens
with tracer.start_as_current_span("validar_itens") as sub_span:
for idx, item in enumerate(itens):
if item.get("quantidade", 0) <= 0:
sub_span.set_attribute(f"item_{idx}_invalido", True)
raise ValueError(f"Item {idx} com quantidade inválida")
Calcular totais
with tracer.start_as_current_span("calcular_totais") as sub_span:
total = sum(item["preco"] * item["quantidade"] for item in itens)
sub_span.set_attribute("total_pedido", total)
Registrar no banco
with tracer.start_as_current_span("persistir_pedido") as sub_span:
Simulando gravação
sub_span.set_attribute("banco_resultado", "sucesso")
return {
"pedido_id": pedido_id,
"status": "confirmado",
"total": total
}
Usar a função
resultado = processar_pedido(1001, [
{"nome": "Produto A", "preco": 50.0, "quantidade": 2},
{"nome": "Produto B", "preco": 30.0, "quantidade": 1}
])</code></pre>
<h2>Contexto, Baggage e Propagação</h2>
<h3>Propagação de Contexto em Sistemas Distribuídos</h3>
<p>Um dos maiores desafios em observabilidade distribuída é manter o contexto (trace_id, span_id, baggage) enquanto uma requisição viaja entre serviços. OpenTelemetry resolve isso com <strong>propagadores</strong>, que injetam informações de contexto em headers HTTP ou mensagens. Quando o serviço B recebe a requisição do serviço A, ele extrai esse contexto do header e continua o trace no mesmo ID. Sem propagadores, cada serviço veria isoladamente, quebrando toda a cadeia de observabilidade.</p>
<pre><code class="language-python"># Exemplo de propagação entre dois serviços (A chama B)
===== SERVIÇO A =====
from opentelemetry import trace
from opentelemetry.propagate import inject
import requests
tracer = trace.get_tracer(__name__)
def chamar_servico_b():
with tracer.start_as_current_span("chamar_servico_b") as span:
span.set_attribute("alvo", "servico_b")
Injetar contexto nos headers
headers = {}
inject(headers)
Fazer requisição com headers propagados
response = requests.get(
"http://localhost:5001/processar",
headers=headers
)
return response.json()
===== SERVIÇO B =====
from flask import Flask, request
from opentelemetry.propagate import extract
from opentelemetry.instrumentation.flask import FlaskInstrumentor
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
@app.route('/processar', methods=['GET'])
def processar():
Extrair contexto da requisição
ctx = extract(request.headers)
tracer = trace.get_tracer(__name__)
O span criado aqui será filho do span do serviço A
with tracer.start_as_current_span("processamento") as span:
span.set_attribute("tempo_processamento_ms", 150)
return {"resultado": "processado"}
if __name__ == '__main__':
app.run(port=5001)</code></pre>
<h3>Baggage para Metadados de Contexto</h3>
<p>Baggage permite passar dados arbitrários (metadados) junto com o contexto através de serviços, sem incluir no payload principal. Um exemplo comum é passar user_id, tenant_id ou customer_tier. Todos os spans e logs de todos os serviços que tocam nessa requisição terão acesso ao baggage automaticamente, permitindo filtros poderosos em sua ferramenta de observabilidade.</p>
<pre><code class="language-python"># Usando Baggage para propagar tenant_id
from opentelemetry.baggage import set_baggage, get_baggage
def handle_requisicao(tenant_id: str, user_id: str):
Definir baggage
set_baggage("tenant_id", tenant_id)
set_baggage("user_id", user_id)
Agora, qualquer span ou log criado terá acesso a isso
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("processar_dados") as span:
Baggage é automaticamente incluído como atributo de contexto
tenant = get_baggage("tenant_id")
user = get_baggage("user_id")
print(f"Processando para tenant={tenant}, user={user}")
Na sua ferramenta de observabilidade (Grafana, DataDog, etc),
você pode fazer queries como:
SELECT * FROM traces WHERE attributes.baggage.tenant_id = "acme-corp"</code></pre>
<h2>Backends e Exportadores: Onde os Dados Vão</h2>
<p>OpenTelemetry coleta dados mas precisa enviá-los a algum lugar. Você pode usar <strong>OTLP</strong> (OpenTelemetry Protocol), que é agnóstico, ou exportadores específicos. OTLP é o padrão moderno: funciona com gRPC ou HTTP, é eficiente e suportado por ferramentas como Jaeger, Tempo (backend de traces do Grafana), Honeycomb, New Relic, DataDog e muitos outros. Para ambiente de desenvolvimento ou prototipagem rápida, use a pilha aberta: Jaeger para traces, Prometheus para métricas e Loki para logs. Para produção, considere soluções como Grafana Cloud, Honeycomb ou New Relic que oferecem suporte profissional.</p>
<pre><code class="language-python"># Exemplo completo integrando tudo com OTLP e Grafana Loki/Prometheus/Tempo
from opentelemetry import trace, metrics, logs
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.logs import LoggerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.log_exporter import OTLPLogExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
import logging
from flask import Flask
===== CONFIGURAÇÃO CENTRALIZADA =====
def setup_observability():
Exportadores OTLP (apontam para collector OpenTelemetry)
trace_exporter = OTLPSpanExporter(insecure=True)
metric_exporter = OTLPMetricExporter(insecure=True)
log_exporter = OTLPLogExporter(insecure=True)
Providers
trace_provider = TracerProvider()
trace_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
trace.set_tracer_provider(trace_provider)
metric_reader = PeriodicExportingMetricReader(metric_exporter)
metric_provider = MeterProvider(metric_readers=[metric_reader])
metrics.set_meter_provider(metric_provider)
log_provider = LoggerProvider()
log_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter))
logs.set_logger_provider(log_provider)
Instrumentações automáticas
FlaskInstrumentor().instrument()
RequestsInstrumentor().instrument()
Logging padrão integrado
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
===== APLICAÇÃO =====
setup_observability()
app = Flask(__name__)
logger = logging.getLogger(__name__)
meter = metrics.get_meter(__name__)
Métrica customizada
request_duration = meter.create_histogram("request_duration_ms")
@app.route('/api/health')
def health():
logger.info("Health check realizado")
return {"status": "healthy"}
@app.route('/api/process/<int:item_id>')
def process_item(item_id):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("process_item") as span:
span.set_attribute("item_id", item_id)
logger.info(f"Iniciando processamento", extra={"item_id": item_id})
try:
Simular processamento
result = {"item_id": item_id, "status": "processed"}
request_duration.record(25)
logger.info("Processamento concluído com sucesso")
return result
except Exception as e:
logger.error(f"Erro ao processar: {e}", exc_info=True)
span.set_attribute("erro", str(e))
return {"error": str(e)}, 500
if __name__ == '__main__':
app.run(debug=False)</code></pre>
<h2>Boas Práticas e Armadilhas Comuns</h2>
<h3>Escolha Nomes Semanticamente Significativos</h3>
<p>Um dos erros mais comuns é nomear spans de forma genérica como <code>"request"</code>, <code>"query"</code> ou <code>"operation"</code>. Quando você está analisando milhões de traces em produção, nomes genéricos são inúteis. Use nomes que descrevam exatamente o que está acontecendo: <code>"fetch_user_from_db"</code>, <code>"validate_payment_card"</code>, <code>"send_notification_email"</code>. O mesmo vale para atributos — use convenções OpenTelemetry quando possível: <code>http.method</code>, <code>db.system</code>, <code>db.operation</code>, <code>exception.type</code>.</p>
<h3>Evite Carregar Dados Sensíveis em Spans e Logs</h3>
<p>Spans e logs viajam para sistemas de observabilidade centralizados. Nunca inclua senhas, tokens de API, números de cartão de crédito ou PII (Personally Identifiable Information) em spans ou logs. Se você precisa correlacionar eventos com usuários, use IDs anônimos ou hashes. Muitas ferramentas oferecem redação automática, mas contar apenas com isso é arriscado.</p>
<h3>Não Crie Spans para Tudo</h3>
<p>Criar um span para cada linha de código é um anti-padrão que gera ruído e sobrecarga. Spans devem representar unidades de trabalho significativas: uma chamada HTTP, uma query de banco, uma chamada a API externa. Operações internas como looping ou condicionais não precisam de spans. Use a regra prática: "Se isso leva menos de 1ms em média, provavelmente não precisa de span".</p>
<h3>Configure Sampling em Produção</h3>
<p>Com tracing contínuo em aplicações de alto volume, você coleta bilhões de traces por hora. Armazenar todos é inviável. Use <strong>sampling</strong>: capture 100% dos traces em desenvolvimento, mas em produção capture apenas 10% aleatoriamente, ou use tail-based sampling (capture apenas traces lentos ou com erro). OpenTelemetry oferece <code>TraceIdRatioBased</code> e <code>ParentBased</code> samplers prontos.</p>
<pre><code class="language-python"># Configurar sampling em produção
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
Amostrar 10% de todas as traces
sampler = TraceIdRatioBased(0.1)
trace_provider = TracerProvider(sampler=sampler)
Ou usar ParentBased: herdar a decisão do serviço pai
from opentelemetry.sdk.trace.sampling import ParentBased
sampler = ParentBased(TraceIdRatioBased(0.1))
trace_provider = TracerProvider(sampler=sampler)</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>OpenTelemetry é o padrão unificado para observabilidade</strong> — traces, métricas e logs em um único SDK, agnóstico a fornecedores. Isso elimina lock-in e oferece flexibilidade real para escolher backends depois. A instrumentação automática reduz boilerplate enquanto instrumentação manual oferece controle para lógica de negócio; os dois trabalham juntos. Por fim, propagação de contexto entre serviços distribuídos e boas práticas como naming semântico, sampling e proteção de dados sensíveis são o que transforma raw telemetria em observabilidade acionável. Comece hoje instrumentando sua aplicação — observabilidade não é um luxo, é uma necessidade em sistemas modernos.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://opentelemetry.io/docs/" target="_blank" rel="noopener noreferrer">OpenTelemetry Official Documentation</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-python" target="_blank" rel="noopener noreferrer">OpenTelemetry Python SDK - GitHub</a></li>
<li><a href="https://www.cncf.io/blog/2022/08/11/opentelemetry-101/" target="_blank" rel="noopener noreferrer">Getting Started with OpenTelemetry - CNCF</a></li>
<li><a href="https://www.jaegertracing.io/docs/" target="_blank" rel="noopener noreferrer">Distributed Tracing with Jaeger and OpenTelemetry</a></li>
<li><a href="https://www.oreilly.com/library/view/observability-engineering/9781492076438/" target="_blank" rel="noopener noreferrer">Observability Engineering - O'Reilly (Charity Majors, Liz Fong-Jones)</a></li>
</ul>
<p><!-- FIM --></p>