Instrumentando FastAPI com OpenTelemetry: traces, métricas e logs correlacionados
Por John Pires · 12 de março de 2026

Instrumentando FastAPI com OpenTelemetry: traces, métricas e logs correlacionados
1) Motivacao e trade-offs
Porque instrumentar
Observability não é um extra: é ferramenta de correção rápida e prevenção. Em aplicações FastAPI, traces ajudam a entender latência entre endpoints, métricas quantificam tráfego e erros, e logs correlacionados permitem seguir a execução com contexto humano. Juntos permitem reduzir MTTR e priorizar correções com dados.
Trade-offs práticos
Instrumentar adiciona dependências, overhead e complexidade operacional. Cada span tem custo; métricas de alta cardinalidade ou registros muito frequentes podem sobrecarregar backends. Escolha amostragem, agregações e exportadores com base em requisitos de latência, custo e volume.
Decisões típicas:
- Sampling: traces completos em staging, taxa reduzida em produção.
- Métricas: exporte agregados, evite etiquetas de alta cardinalidade.
- Logs: correlacione ids, não replique span fields nos logs.
Visibilidade vs Performance
Instrumentação deve ser incremental. Comece por traces de alto nível e métricas SLO, depois adicione spans críticos (DB, chamadas externas). Use processadores assíncronos para não bloquear requests.
Resumo executivo
Instrumente com propósito: trace para latência, métricas para saúde e logs para inspecionar falhas. Configure amostragem e exportadores para equilibrar custo e fidelidade.
2) Fundamentos tecnicos
Componentes OpenTelemetry
OpenTelemetry tem três pilares no stack Python: API, SDK e Exporters. API define interfaces de uso; SDK provê implementações (TracerProvider, MeterProvider); Exporters enviam dados a collector/observability backend (OTLP, Jaeger, Prometheus).
O papel do Resource e providers
Resource descreve atributos da instrumentação: service.name, service.instance.id, deployment.environment. Configure TracerProvider e MeterProvider com esse Resource antes de instrumentar frameworks. Se instanciar providers depois, instrumentação automática pode usar providers default, levando a dados perdidos.
Instrumentacao manual vs automática
Auto-instrumentation é rápida (ex: FastAPIInstrumentor, instrumentadores de httpx/sqlalchemy). Perfil prático: use auto para cobertura rápida e adicione spans manuais onde precisa de contexto e tags customizadas.
Correlacao logs-trace
Python ainda não tem um spec de logs OpenTelemetry totalmente unificado, então a prática comum é enriquecer logs com trace_id/span_id via Formatter ou contextvars. Isso permite buscar logs por trace em backends tradicionais (ELK, Loki).
3) Exemplo 1: caso simples
Objetivo do exemplo
Mostrar como instrumentar um app FastAPI com traces exportados via OTLP HTTP para um collector local. Inclui configuração básica de TracerProvider e FastAPIInstrumentor para traces automáticos de rotas.
Codigo: FastAPI + OTLP exporter (traces)
Este trecho cria TracerProvider com BatchSpanProcessor e instrumenta a app FastAPI. O collector espera em localhost:4318 (OTLP HTTP).
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
# Configura provider e exporter
resource = Resource.create({
'service.name': 'minha-api',
'service.version': '1.0.0'
})
provider = TracerProvider(resource=resource)
otlp_exporter = OTLPSpanExporter(endpoint='http://localhost:4318/v1/traces')
provider.add_span_processor(BatchSpanProcessor(notlp_exporter))
trace.set_tracer_provider(provider)
# App FastAPI
app = FastAPI()
FastAPIInstrumentor().instrument_app(app)
@app.get('/items/{item_id}')
async def read_item(item_id: int):
return {'item_id': item_id}
O que este código faz: inicializa TracerProvider com recurso customizado, adiciona um BatchSpanProcessor que envia spans via OTLP HTTP e instrumenta a app FastAPI para gerar spans automáticos por request.
Resultado: cada chamada a /items/{id} gera um span root com atributos HTTP. O collector em localhost:4318 receberá spans e você poderá visualizar no backend configurado.
Explicacoes e cuidados
Use BatchSpanProcessor para não bloquear. Se estiver em ambiente serverless, prefira exportadores síncronos com flush no shutdown. Garanta que a instrumentação ocorra antes de criar rotas que possam iniciar threads ou conexões.
4) Exemplo 2: caso avancado do mundo real
Contexto real
Aplicação FastAPI que faz queries a um banco via SQLAlchemy, realiza chamadas HTTP a um serviço externo com httpx, e precisa exportar métricas e traces, além de correlacionar logs para debug operacional.
Componentes a cobrir
Vamos combinar:
- Traces automáticos em FastAPI e httpx.
- Spans manuais para queries SQL críticas.
- Métricas: contador de requisições e histograma de latência.
- Logs correlacionados com trace_id/span_id via Formatter.
Codigo: app completo com spans manuais, metrics e logs correlacionados
O código abaixo demonstra configuração integrada. É funcional com dependências recentes de opentelemetry e assume um collector OTLP em localhost.
import logging
import time
import httpx
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, text
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.metrics import set_meter_provider, get_meter
from opentelemetry.sdk.metrics import MeterProvider, Counter, Histogram
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.trace import get_tracer
# Logging setup com correlacao
from opentelemetry.trace import get_current_span
class OTelFormatter(logging.Formatter):
def format(self, record):
span = get_current_span()
ctx = span.get_span_context()
record.trace_id = format(ctx.trace_id, '032x') if ctx.trace_id else ''
record.span_id = format(ctx.span_id, '016x') if ctx.span_id else ''
return super().format(record)
handler = logging.StreamHandler()
handler.setFormatter(OTelFormatter('[%(asctime)s] %(levelname)s %(trace_id)s %(span_id)s %(message)s'))
root = logging.getLogger()
root.setLevel(logging.INFO)
root.handlers = [handler]
logger = logging.getLogger('app')
# Trace provider
resource = Resource.create({'service.name': 'api-com-db'})
tracer_provider = TracerProvider(resource=resource)
otlp_trace = OTLPSpanExporter(endpoint='http://localhost:4318/v1/traces')
tracer_provider.add_span_processor(BatchSpanProcessor(otlp_trace))
trace.set_tracer_provider(tracer_provider)
tracer = get_tracer(__name__)
# Metrics provider
metric_exporter = OTLPMetricExporter(endpoint='http://localhost:4318/v1/metrics')
reader = PeriodicExportingMetricReader(metric_exporter, export_interval_millis=5000)
meter_provider = MeterProvider(resource=resource, metric_readers=[reader])
set_meter_provider(meter_provider)
meter = get_meter(__name__)
request_counter = meter.create_counter('http.server.requests')
latency_hist = meter.create_histogram('http.server.request_latency_ms')
# DB engine
engine = create_engine('sqlite:///./test.db', connect_args={'check_same_thread': False})
app = FastAPI()
FastAPIInstrumentor().instrument_app(app)
HTTPXClientInstrumentor().instrument()
@app.get('/search')
async def search(q: str):
start = time.time()
with tracer.start_as_current_span('search.handler') as span:
span.set_attribute('query', q)
# Span manual para DB
with tracer.start_as_current_span('db.query') as db_span:
db_span.set_attribute('db.system', 'sqlite')
with engine.connect() as conn:
result = conn.execute(text('SELECT 1'))
_ = result.fetchone()
# External call
async with httpx.AsyncClient() as client:
resp = await client.get('https://httpbin.org/get')
span.set_attribute('httpbin.status', resp.status_code)
duration_ms = (time.time() - start) * 1000
request_counter.add(1, {'route': '/search'})
latency_hist.record(duration_ms, {'route': '/search'})
logger.info('search executed', extra={'q': q})
return {'q': q, 'status': resp.status_code}
O que acontece:
- Traces automáticos para a rota gerados por FastAPIInstrumentor.
- Spans manuais 'search.handler' e 'db.query' para detalhar latências críticas.
- Métricas: contador e histograma exportados periodicamente via OTLPMetricExporter.
- Logs com trace_id/span_id adicionados pelo OTelFormatter.
Notas de implementacao
Coloque a configuração de providers no startup do processo, antes de criar clientes/engines que possam iniciar threads. Para aplicações ASGI que usam reload, cuidado com múltiplas inicializações duplicando exportadores; detecte env de reload e execute apenas no processo principal.
Observacoes sobre métricas
MeterProvider e exporters de métricas são relativamente novos em Python. Confirmar compatibilidade de versões entre opentelemetry-sdk, exportadores e instrumentations. Em produção, ajuste export_interval e tamanho de buffer para equilibrar custo e latência.
5) Debugging e troubleshooting
Erros comuns
Listagem de armadilhas que eu vejo com frequência:
- Instrumentadores rodando antes do TracerProvider configurado — causa spans sem provider e dados não exportados.
- OTLP endpoint incorreto (porta gRPC 4317 vs HTTP 4318) — verificar URL e protocolo.
- Versões incompatíveis entre instrumentations e SDK — confira changelogs e use versões alinhadas.
- Métricas não aparecendo por usar reader incorreto ou esquecer set_meter_provider.
- Logs sem trace_id porque Formatter usa get_current_span fora do contexto do request.
Dica: isole provider e instrumentação em um módulo único e importe ele antes de iniciar a app. Isso reduz chances de inicialização duplicada ou fora de ordem.
Como diagnosticar
Passos práticos:
- 1) Verifique se spans são criados localmente: adicione um SpanProcessor que escreve spans no stdout temporariamente.
- 2) Teste exportador com um script simples que cria um span e faz flush no final.
- 3) Confirme que Collector ou backend está recebendo tráfego: use tcpdump, netstat ou logs do collector.
Debug: testar exporter manualmente
Exemplo rápido para validar que o OTLPTraceExporter alcança o collector. Execute separadamente para isolar problemas de app/ASGI.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
exporter = OTLPSpanExporter(endpoint='http://localhost:4318/v1/traces')
provider.add_span_processor(SimpleSpanProcessor(exporter))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer('manual-test')
with tracer.start_as_current_span('manual-test-span') as span:
span.set_attribute('test', True)
print('span created and exported (check collector logs)')
Se o collector não receber, verifique endpoint, firewall, e versão do exporter (HTTP vs gRPC). Use SimpleSpanProcessor para testes porque é síncrono e mais fácil de observar.
Armadilhas especificas de FastAPI/uvicorn
Uvicorn com reload cria processos filhos que podem re-inicializar providers. Para evitar duplicação, verifique variáveis de ambiente como 'PYTHONINSPECT' ou rode sem reload em produção. Outra armadilha: executar instrumentacao no módulo de rotas em vez do módulo principal, o que pode causar instrumentação parcial.
6) Conclusao e passos praticos
Checklist para entrega
Ao finalizar instrumentação de um serviço FastAPI, valide o seguinte:
- Providers (Tracer/Meter) configurados antes da instrumentação.
- Batch/Periodic readers configurados para produção.
- Sampling definido de acordo com custo e necessidade de investigação.
- Logs correlacionados com trace_id e span_id para facilitar investigação.
- Métricas com labels limitados para evitar cardinalidade alta.
Recomendações finais
Instrumentação é um processo iterativo. Comece com traces de alto nível e métricas SLO, adicione spans e logs conforme incidentes surgem. Automatize testes para garantir que spans e métricas sejam enviados em pipelines CI quando aplicável.
Implementações bem-sucedidas seguem um padrão: proveito das instrumentações automáticas, spans manuais apenas para casos críticos, métricas agregadas e logs enriquecidos. Isso oferece observabilidade prática com custo controlado.
Recursos adicionais
Consulte a documentação oficial do OpenTelemetry Python, exemplos dos instrumentadores (fastapi, httpx, sqlalchemy) e guias do seu backend (OTel Collector, Grafana Tempo, Prometheus). Mantendo versões compatíveis você evita a maior parte das surpresas operacionais.
Implementando as práticas descritas você terá traces, métricas e logs correlacionados de forma consistente, com baixo risco de overhead descontrolado e capacidade real de investigação de problemas em produção.
Assuntos: Python, opentelemetry, tracing, observability
Leia também
Pydantic v2: migrando modelos, validators custom e padrões de performance
Guia prático para migrar modelos do Pydantic v1 para v2: reescrever validators com @field_validator, usar model_construct e slots para performance, patterns…
Servidor de embeddings com FastAPI, Transformers e FAISS: batching e quantização
Implementação prática de um servidor de embeddings com FastAPI, transformers e FAISS: mostro anti-patterns e correções, batching assíncrono, execução em…
Uploads resumíveis em FastAPI: S3 multipart, checkpoints e revalidação
Como projetar uploads resumíveis em FastAPI usando S3 multipart: patterns corretos, checkpoints cliente/servidor, revalidação e jobs de limpeza. Evite…
FastAPI: endpoints idempotentes com Idempotency-Key e Redis (retries seguros)
Guia prático para implementar Idempotency-Key em FastAPI com Redis: locks atomicos, persistencia de respostas, scripts Lua, decorator para endpoints e boas…
Configuração segura em Python: Pydantic v2 + HashiCorp Vault na prática
Pydantic v2 + HashiCorp Vault: técnicas práticas para evitar vazamento de segredos, integrar Vault como fonte de configuração, e patterns corretos para…
FastAPI: inferência de modelos em streaming com asyncio, chunking e SSE
Padrões práticos para inferência em streaming com FastAPI: use asyncio.Queue, chunking e SSE para reduzir latência percebida, controlar concorrência e…