OpenTelemetry com Python: Logs, Métricas e Traces
Aprenda a usar OpenTelemetry em Python para instrumentar uma API FastAPI com traces, métricas, logs correlacionados e boas práticas de observabilidade.
Observabilidade deixou de ser um luxo de times grandes. Em 2026, muita vaga de backend, dados, SRE, plataforma e IA já espera que a pessoa desenvolvedora saiba responder perguntas simples sobre um sistema em produção: a requisição falhou onde? Qual endpoint ficou lento? Quantas chamadas deram erro depois do deploy? O problema está no banco, em uma API externa ou no próprio código Python?
Logs soltos ajudam, mas não contam a história completa. Métricas mostram volume e latência, mas nem sempre explicam a causa. Traces mostram o caminho de uma requisição, mas precisam estar ligados aos logs para serem úteis no dia a dia. É aí que entra o OpenTelemetry, um padrão aberto para coletar sinais de observabilidade sem prender seu projeto a uma ferramenta específica.
Neste guia, vamos montar uma base prática de observabilidade em Python com FastAPI, opentelemetry-sdk, instrumentação automática e exemplos de logs correlacionados. Se você ainda está estruturando uma API, leia primeiro API com FastAPI. Se quer fortalecer a base antes de produção, veja também logging em Python, testes com pytest e deploy de aplicação Python.
O que o OpenTelemetry resolve
OpenTelemetry é um conjunto de especificações, bibliotecas e ferramentas para gerar, coletar e exportar telemetria. Na prática, ele padroniza três sinais principais:
- traces: mostram o caminho de uma requisição entre funções, serviços e dependências externas;
- métricas: registram contadores, histogramas, latência, taxa de erro e uso de recursos;
- logs: guardam eventos textuais ou estruturados que explicam decisões do sistema.
A vantagem é que seu código não precisa depender diretamente de uma plataforma específica. Você pode exportar para um collector, Prometheus, Grafana, Jaeger, Tempo, Datadog, New Relic, Honeycomb ou outra ferramenta compatível. Para um projeto de portfólio, isso já passa uma mensagem forte: você não escreveu apenas uma API que responde 200 OK; você pensou em como operá-la.
Exemplo de projeto
Vamos imaginar uma API simples de pedidos:
api-pedidos/
app/
main.py
observability.py
orders.py
pyproject.toml
README.md
Instale as dependências principais:
python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn opentelemetry-sdk opentelemetry-api
pip install opentelemetry-instrumentation-fastapi opentelemetry-exporter-otlp
Em produção, é comum enviar dados para um OpenTelemetry Collector usando OTLP. No ambiente local, você pode começar exportando traces no console para entender o formato antes de ligar Grafana Tempo, Jaeger ou uma plataforma SaaS.
Configurando traces
Crie um arquivo app/observability.py para concentrar a configuração. Isso evita espalhar setup de telemetria pela aplicação.
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
def configurar_tracing(service_name: str, ambiente: str = "local") -> None:
resource = Resource.create({
"service.name": service_name,
"deployment.environment": ambiente,
})
provider = TracerProvider(resource=resource)
if ambiente == "local":
exporter = ConsoleSpanExporter()
else:
exporter = OTLPSpanExporter()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
Esse código define dois atributos importantes. service.name identifica a aplicação nos painéis, enquanto deployment.environment separa local, staging e produção. Parece detalhe, mas sem isso você mistura dados de teste com dados reais e dificulta qualquer investigação.
Instrumentando FastAPI
Agora conecte a instrumentação automática no main.py:
import os
import time
from fastapi import FastAPI, HTTPException
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from app.observability import configurar_tracing
ambiente = os.getenv("APP_ENV", "local")
configurar_tracing("api-pedidos-python", ambiente)
app = FastAPI(title="API de Pedidos")
FastAPIInstrumentor.instrument_app(app)
tracer = trace.get_tracer(__name__)
@app.get("/pedidos/{pedido_id}")
def buscar_pedido(pedido_id: int):
with tracer.start_as_current_span("buscar_pedido_no_banco") as span:
span.set_attribute("pedido.id", pedido_id)
time.sleep(0.05)
if pedido_id == 404:
span.set_attribute("pedido.encontrado", False)
raise HTTPException(status_code=404, detail="Pedido não encontrado")
span.set_attribute("pedido.encontrado", True)
return {"id": pedido_id, "status": "pago", "total": 149.90}
Execute a API:
uvicorn app.main:app --reload
Ao acessar http://localhost:8000/pedidos/123, a instrumentação do FastAPI cria spans para a requisição HTTP. O span manual buscar_pedido_no_banco adiciona contexto de negócio. Em um sistema real, você poderia criar spans para consulta SQL, chamada HTTP externa, validação antifraude, envio para fila ou geração de nota fiscal.
O que colocar em atributos
A regra é: atributo bom ajuda a filtrar investigação sem vazar dados sensíveis. Bons exemplos:
- nome do endpoint;
- método HTTP;
- código de status;
- ambiente;
- versão do serviço;
- tipo de operação;
- identificador interno não sensível;
- nome da fila, tabela ou integração.
Evite CPF, e-mail, telefone, endereço, token, payload completo e qualquer dado pessoal desnecessário. Observabilidade não pode virar vazamento organizado. Para projetos brasileiros, esse cuidado conversa diretamente com LGPD e com o bom senso de produção.
Um padrão seguro é registrar pedido.id se ele for um identificador interno, mas não registrar cliente.cpf. Se precisar depurar um caso específico, use uma estratégia controlada, com permissão, retenção curta e mascaramento.
Logs correlacionados com traces
Logs continuam úteis, principalmente para decisões de negócio e mensagens de erro. O ganho vem de incluir o trace_id e o span_id, permitindo sair de um log para o trace completo.
import logging
from opentelemetry import trace
logger = logging.getLogger("api-pedidos")
def log_info(mensagem: str, **campos: object) -> None:
span = trace.get_current_span()
contexto = span.get_span_context()
logger.info(
mensagem,
extra={
"trace_id": format(contexto.trace_id, "032x"),
"span_id": format(contexto.span_id, "016x"),
**campos,
},
)
Em um projeto pequeno, você pode começar com logging padrão. Em um projeto maior, vale estudar logs em JSON com structlog ou configuração de logging.Formatter própria. O ponto não é a biblioteca, e sim a disciplina: logar eventos relevantes, com campos consistentes e sem dados sensíveis.
Métricas que importam
Nem toda métrica precisa virar gráfico. Para uma API Python, comece pelo conjunto que realmente ajuda operação:
| Métrica | Por que importa |
|---|---|
| requisições por endpoint | mostra volume e mudança de tráfego |
| taxa de erro por status | separa falhas 4xx e 5xx |
| latência p50/p95/p99 | mostra lentidão que média esconde |
| chamadas a APIs externas | identifica dependências problemáticas |
| tamanho de fila | revela gargalo assíncrono |
| tempo de job | mostra degradação em automações |
Se sua API usa tarefas em segundo plano, Celery, Redis ou filas, combine métricas com logs claros de início, fim, erro e retry. O artigo sobre FastAPI, background tasks, Celery e Redis aprofunda essa parte.
Usando OTLP em produção
No ambiente real, a aplicação normalmente exporta para um collector:
export APP_ENV=production
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
uvicorn app.main:app --host 0.0.0.0 --port 8000
O collector recebe telemetria de vários serviços, aplica filtros, adiciona atributos e encaminha para os backends finais. Essa arquitetura é útil porque você troca destino sem redeployar toda aplicação. Também evita que cada serviço precise conhecer detalhes de autenticação de cada ferramenta.
Para quem trabalha com múltiplas linguagens, esse padrão conversa bem com ecossistemas como Go. Se você mantém serviços Python e Go no mesmo time, vale comparar a abordagem com o guia de logging estruturado com slog em Go, já que OpenTelemetry é justamente mais forte quando padroniza observabilidade entre stacks diferentes.
Checklist para portfólio
Um bom projeto de portfólio não precisa simular uma empresa inteira. Mas ele deve provar maturidade. Inclua no README:
- como rodar a API localmente;
- como ver traces no console;
- quais variáveis OTLP configurar;
- quais atributos são seguros;
- quais dados nunca devem ir para logs;
- exemplo de investigação de erro;
- screenshot ou descrição do painel local, se usar Jaeger ou Grafana.
Também vale criar um endpoint propositalmente lento ou uma rota que chama uma API externa fake. Assim você demonstra latência, erro, retry e trace em uma história compreensível.
Erros comuns
O primeiro erro é instrumentar tudo e não olhar nada. Observabilidade boa nasce de perguntas: qual rota falha mais? O deploy piorou a latência? O banco ficou lento ou foi uma integração externa? Sem pergunta, você só acumula ruído.
O segundo erro é registrar dados demais. Payload completo em log parece útil no começo, mas vira risco de privacidade, custo e confusão. Registre IDs, contagens, tipos e estados. Use amostras controladas quando necessário.
O terceiro erro é deixar ambiente local, staging e produção com o mesmo service.name e sem atributos de ambiente. Quando o painel mistura tudo, a equipe perde confiança na ferramenta.
O quarto erro é esquecer deploy. Se o serviço roda em container, garanta que as variáveis OTEL_* estejam documentadas no compose, no Kubernetes manifest ou na plataforma usada. Observabilidade que só funciona no notebook do desenvolvedor não ajuda em incidente.
Próximos passos
Depois de configurar traces básicos, evolua em camadas. Primeiro, padronize logs e atributos. Depois, exporte para um collector local com Docker Compose. Em seguida, adicione métricas de latência e taxa de erro. Só então pense em alertas.
Para carreira, esse tema rende um projeto muito forte: uma API FastAPI com banco PostgreSQL, autenticação simples, testes, OpenTelemetry, logs estruturados e deploy documentado. Ele mostra backend, operação, segurança básica e capacidade de explicar problemas em produção, exatamente o tipo de maturidade que diferencia uma pessoa que apenas escreve endpoints de alguém pronta para cuidar de sistemas reais.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português