Guía práctica de observabilidad en FastAPI: haz crecer una API que puedas “ver” con logs estructurados, métricas, trazas y monitoreo de errores
Resumen (la visión general primero)
- En operaciones de FastAPI en producción, más aterrador que los bugs es no saber qué está pasando. La clave para evitarlo es la observabilidad: combinar logs, métricas, trazas y monitoreo de errores.
- Emite logs como logs estructurados (JSON) para poder rastrear IDs de solicitud, usuarios y eventos de negocio importantes.
- Usa Prometheus y herramientas como
prometheus-fastapi-instrumentatorpara visualizar latencia, tasa de error y throughput como números. - Usa OpenTelemetry y
opentelemetry-instrumentation-fastapipara visualizar el recorrido de ejecución de una sola petición de extremo a extremo, de modo que los cuellos de botella y las llamadas externas queden claros. - Integra Sentry (o similar) en FastAPI para recopilar automáticamente stack traces, detalles de la petición y contexto del usuario cuando ocurren excepciones.
A quién le beneficia leer esto (personas concretas)
-
Aprendiz A (dev en solitario / proyectos paralelos)
Ejecuta FastAPI en Heroku o un VPS, pero sufre con “a veces se cae y no sé por qué” o “los logs están dispersos y no puedo seguir nada.”
→ Con logs estructurados + Sentry, el objetivo es llegar al menos a un estado donde “cuando ocurre un error, me notifican y puedo investigar la causa raíz.” -
Equipo pequeño B (estudio de 3–5 devs)
Construyen sistemas de negocio pequeños/medianos con FastAPI, pero las investigaciones de incidentes siempre llevan tiempo.
→ Con métricas de Prometheus + trazas de OpenTelemetry, puedes identificar rápido “qué endpoint se volvió lento y cuándo” y “qué API externa es el cuello de botella.” -
Dev SaaS C (startup)
Despliega features con frecuencia y quiere una postura de “release sin miedo.”
→ Diseña logs/métricas/trazas/monitoreo de errores en conjunto y configura dashboards para detectar regresiones de performance y aumento de errores en tiempo real.
1. Ordenar los tres pilares de la observabilidad
Primero, alinea “qué observar y cómo.”
1.1 Los tres pilares
-
Logs
- Registros textuales: excepciones, eventos de negocio, información de depuración.
- Flexibles en formato, pero a menos que los estructures (JSON), la agregación/búsqueda posterior se vuelve dolorosa.
-
Métricas
- Datos numéricos de series temporales: RPS, latencia, tasa de error, CPU, memoria, etc.
- Normalmente se exponen en formato Prometheus y se grafican en Grafana.
-
Trazas (traces)
- Registros del “viaje de una sola petición”, incluyendo por qué servicios/pasos pasó y cuánto tardó cada uno.
- Suelen construirse con OpenTelemetry + Jaeger / Tempo / Application Insights, etc.
Complementando esto está el monitoreo de errores (p. ej., Sentry):
- Recopila automáticamente stack traces, headers de la petición, contexto del usuario y envía notificaciones cuando ocurren excepciones.
2. Logs: logging estructurado e IDs de solicitud
2.1 Aplicar lo básico de logging de Python a FastAPI
FastAPI no trae su propio sistema de logs: normalmente usa el módulo estándar de Python logging combinado con la configuración de logging de Uvicorn.
Empieza preparando un formatter que emita JSON.
# app/core/logging.py
import json
import logging
import sys
from typing import Any
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
payload: dict[str, Any] = {
"level": record.levelname,
"logger": record.name,
"msg": record.getMessage(),
}
if hasattr(record, "request_id"):
payload["request_id"] = record.request_id
if record.exc_info:
payload["exc_info"] = self.formatException(record.exc_info)
return json.dumps(payload, ensure_ascii=False)
def setup_logging(level: str = "INFO") -> None:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
root = logging.getLogger()
root.handlers[:] = [handler]
root.setLevel(level)
2.2 Middleware para adjuntar un request ID
Para que los logs sean más fáciles de seguir, adjunta un ID con alcance por petición para correlacionar todos los logs de la misma solicitud.
# app/middleware/request_id.py
import uuid
from starlette.types import ASGIApp, Receive, Scope, Send
class RequestIDMiddleware:
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
if scope["type"] != "http":
return await self.app(scope, receive, send)
request_id = str(uuid.uuid4())
scope["state"] = scope.get("state", {})
scope["state"]["request_id"] = request_id
async def send_wrapper(message):
if message["type"] == "http.response.start":
headers = [(b"x-request-id", request_id.encode())]
message.setdefault("headers", []).extend(headers)
await send(message)
await self.app(scope, receive, send_wrapper)
2.3 Propagar el request ID a través del logger
Recibe request mediante una dependencia de FastAPI y prepara un helper que incluya request_id en la salida del log.
# app/deps/logging.py
import logging
from fastapi import Request
def get_logger(request: Request) -> logging.LoggerAdapter:
base = logging.getLogger("app")
request_id = getattr(request.state, "request_id", None)
return logging.LoggerAdapter(base, extra={"request_id": request_id})
# app/main.py
from fastapi import FastAPI, Depends
from app.core.logging import setup_logging
from app.middleware.request_id import RequestIDMiddleware
from app.deps.logging import get_logger
setup_logging()
app = FastAPI(title="Observable API")
app.add_middleware(RequestIDMiddleware)
@app.get("/hello")
def hello(logger = Depends(get_logger)):
logger.info("hello endpoint called")
return {"message": "hello"}
Ahora tus logs serán JSON e incluirán request_id, y los clientes también recibirán el header de respuesta X-Request-ID. Una vez que tu agregador de logs (Loki, Cloud Logging, etc.) pueda buscar por ese ID, la investigación de incidentes será mucho más fácil.
3. Métricas: vigila latencia y tasa de errores con Prometheus
3.1 Conceptos básicos de métricas Prometheus
Para métricas, estas tres (las “RED metrics”) son especialmente importantes:
- Rate: cantidad de requests (RPS)
- Errors: ratio de error (4xx/5xx)
- Duration: latencia (P50/P95/P99)
En FastAPI, prometheus-fastapi-instrumentator facilita medir y exportarlas.
3.2 Instalación y setup básico
pip install prometheus-fastapi-instrumentator prometheus-client
# app/metrics.py
from prometheus_fastapi_instrumentator import Instrumentator
def setup_metrics(app):
Instrumentator().instrument(app).expose(app, endpoint="/metrics")
# app/main.py
from fastapi import FastAPI
from app.metrics import setup_metrics
app = FastAPI(title="Observable API")
setup_metrics(app)
Esto habilita métricas en formato Prometheus en /metrics.
Ejemplo:
http_request_duration_seconds_bucket{method="GET",path="/hello",status="2xx",le="0.1"} 42http_requests_total{method="GET",path="/hello",status="2xx"} 100
3.3 Soporte multiproceso con Gunicorn, etc.
Con setups multiproceso como Gunicorn + UvicornWorker, debes habilitar el modo multiproceso del cliente de Prometheus. prometheus-fastapi-instrumentator soporta esto configurando prometheus_multiproc_dir.
export prometheus_multiproc_dir=./metrics
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker
En operaciones reales, Prometheus scrapea /metrics y Grafana lo visualiza con dashboards.
4. Trazado: sigue el viaje de una petición con OpenTelemetry
Las métricas te dicen “dónde tiende a ser lento”, pero el trazado ayuda a responder “¿por qué esta petición en particular fue lenta?”
4.1 Instrumentación de OpenTelemetry para FastAPI
OpenTelemetry ofrece auto-instrumentación para FastAPI mediante opentelemetry-instrumentation-fastapi.
pip install \
opentelemetry-sdk \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation-fastapi \
opentelemetry-instrumentation-logging \
opentelemetry-instrumentation-requests \
opentelemetry-instrumentation-httpx
4.2 Ejemplo mínimo (export OTLP)
# app/otel.py
from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
def setup_tracing(service_name: str = "fastapi-app"):
resource = Resource(attributes={
SERVICE_NAME: service_name,
})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter())
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# app/main.py
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from app.otel import setup_tracing
setup_tracing(service_name="observable-fastapi")
app = FastAPI(title="Observable API")
FastAPIInstrumentor.instrument_app(app)
LoggingInstrumentor().instrument()
RequestsInstrumentor().instrument()
HTTPXClientInstrumentor().instrument()
El exportador OTLP es un protocolo común para enviar trazas a OpenTelemetry Collector o a varios APM (Grafana Tempo, Jaeger, Azure Monitor, Datadog, etc.).
4.3 Qué podrás ver
- Cada petición genera un span raíz, con tags como nombre del endpoint y código de estado
- Consultas a BD y llamadas HTTP externas (requests/httpx) aparecen como spans hijos
- Los dashboards muestran visualmente dónde gastó tiempo cada petición (en ms)
Esto facilita detectar cosas como “este endpoint es lento solo en ciertos periodos” o “hay timeouts frecuentes contra esta API externa”.
5. Monitoreo de errores: no te pierdas excepciones con Sentry
5.1 Integración del SDK de Sentry con FastAPI
Sentry provee un SDK y guías de integración para Python/FastAPI.
Instala:
pip install "sentry-sdk[fastapi]"
La inicialización básica es simple:
# app/sentry_setup.py
import sentry_sdk
def setup_sentry(dsn: str, env: str = "dev"):
sentry_sdk.init(
dsn=dsn,
enable_tracing=True, # Habilita monitoreo de performance
traces_sample_rate=0.1, # Ajusta el muestreo según necesidad
environment=env,
)
# app/main.py
from fastapi import FastAPI
from sentry_sdk.integrations.fastapi import FastApiIntegration
from app.sentry_setup import setup_sentry
setup_sentry(dsn="https://examplePublicKey@o0.ingest.sentry.io/0", env="prod")
app = FastAPI(title="Observable API", lifespan=None)
# La integración de FastAPI de sentry-sdk se habilita automáticamente al inicializar (sigue la guía).
5.2 Qué información se envía
- Stack traces al momento de la excepción
- URL de la petición, método HTTP, headers, query params, etc.
- (Si se configura) contexto como user ID y organization ID
En el dashboard, errores similares se agrupan y puedes ver “desde cuándo aumentó” o “qué versión lo causó”. Si también añades Sentry al frontend, puedes rastrear qué pantalla disparó un error backend.
6. Cómo combinar las cuatro cosas: guías de diseño
Hasta aquí vimos logs, métricas, trazas y monitoreo de errores por separado. Así es como conviene pensar su combinación.
6.1 El objetivo del diseño de observabilidad
- Cuando ocurre un incidente, puedes obtener una causa probable en 5–10 minutos
- Tras un release, puedes detectar rápido regresiones de latencia/tasa de error
- Durante tuning de performance, puedes decidir objetivamente qué mejorar
Trabajando hacia atrás desde eso, define roles como:
- Logs: contexto detallado (params, eventos de negocio)
- Métricas: salud general (métricas RED)
- Trazas: viaje por petición y cuellos de botella
- Monitoreo de errores: recolección de excepciones + notificación + estimación de alcance
6.2 Puntos de observación según el caso de uso
-
Cuando quieres perseguir problemas de rendimiento
- Usa métricas para identificar cuándo sube la latencia
- Inspecciona trazas de muestra en ese periodo para ver qué servicio/consulta externa está lenta
- Añade logs detallados alrededor de rutas de código sospechosas y reproduce/verifica
-
Cuando quieres perseguir aumento de errores
- Revisa Sentry para errores que aumentan recientemente (compara con momentos de release)
- Inspecciona trazas de muestra para comparar “patrones de éxito vs fallo”
- Si hace falta, loguea claves de negocio (p. ej., order ID) para identificar alcance de impacto
Cuando este flujo se vuelve rutina, te acercas a “desplegar sin miedo”.
7. Setup de ejemplo: uno realista para un equipo pequeño
7.1 Tres entornos: dev → staging → prod
-
Desarrollo (dev)
- Logs: salida a consola (JSON)
- Métricas: Prometheus local (opcional)
- Trazas: Jaeger local (opcional)
- Sentry: desactivado o proyecto separado
-
Staging (stg)
- Logs: enviados a logging centralizado
- Métricas: Prometheus + Grafana de staging
- Trazas: backend de trazas en staging
- Sentry: activado con
environment=stg
-
Producción (prod)
- Logs: backend de logs de producción (Loki/Cloud Logging, etc.)
- Métricas: Prometheus + Grafana de producción, dashboards y alertas
- Trazas: backend de trazas en producción (muestreo ajustado)
- Sentry:
environment=prod, integraciones de alertas (Slack/email)
7.2 Inicialización común del lado de FastAPI
# app/bootstrap.py
from app.core.logging import setup_logging
from app.otel import setup_tracing
from app.sentry_setup import setup_sentry
from app.metrics import setup_metrics
from app.core.settings import get_settings
def bootstrap(app):
settings = get_settings()
setup_logging(settings.log_level)
setup_tracing(service_name=settings.app_name)
if settings.sentry_dsn:
setup_sentry(dsn=settings.sentry_dsn, env=settings.env)
setup_metrics(app)
# app/main.py
from fastapi import FastAPI
from app.bootstrap import bootstrap
from app.middleware.request_id import RequestIDMiddleware
app = FastAPI(title="Observable API")
bootstrap(app)
app.add_middleware(RequestIDMiddleware)
Los valores de configuración (DSN, endpoint OTLP, nombre de entorno, etc.) pueden inyectarse vía variables de entorno usando herramientas como pydantic-settings, facilitando cambiar por entorno.
8. Errores comunes y contramedidas
| Síntoma | Causa | Solución |
|---|---|---|
| Los logs son un desastre y difíciles de buscar | Texto plano / formatos inconsistentes | Estructurar como JSON + emitir siempre campos clave (request_id, user_id) |
/metrics se vuelve pesado |
Demasiadas labels / alta cardinalidad | No usar valores extremadamente únicos (p. ej., user ID) como labels |
| Demasiados datos de trazas → sube el coste | Enviar todas las trazas sin muestreo | Ajustar samples_rate / políticas de muestreo por entorno |
| Mucho ruido en Sentry | Enviar incluso errores esperados | Manejar “errores de negocio esperados” y no enviarlos a Sentry |
| Confusión porque dev y prod muestran info muy distinta | Grandes diferencias de config/entorno | Minimizar diferencias y separar claramente env y environment |
9. Hoja de ruta de adopción (paso a paso)
Por último, aquí tienes un camino por etapas para introducir observabilidad en la práctica.
- Logs estructurados + request IDs
- Añadir logs JSON +
X-Request-ID, verificar consistencia localmente y en staging.
- Añadir logs JSON +
- Monitoreo de errores (Sentry, etc.)
- Asegurar que las excepciones disparen notificaciones y abordar primero los errores más urgentes.
- Métricas Prometheus + dashboards Grafana
- Construir dashboards centrados en métricas RED y observar horas pico y comportamiento post-release.
- Trazas OpenTelemetry
- Enfocarse en endpoints lentos/llamadas externas y usar trazas para ubicar cuellos de botella.
- Alertas y diseño de SLO
- Definir umbrales y reglas de alerta para métricas como latencia P95 y tasa de error.
- Mejora continua
- Con cada feature nueva, crear el hábito de preguntar: “¿Cómo vamos a observar esto?”
Enlaces de referencia (para profundizar)
-
FastAPI
-
Logging
-
Métricas / Prometheus
-
OpenTelemetry / Trazado
-
Monitoreo de errores (Sentry)
-
Visión general de observabilidad
Conclusión
- En operaciones de FastAPI en producción, no se trata solo de velocidad: poder ver lo que está pasando importa. Combinando logs, métricas, trazas y monitoreo de errores, puedes detectar incidentes y regresiones rápido y rastrear la causa raíz.
- Logs estructurados + request IDs son un primer paso universalmente útil para cualquier escala de proyecto. Desde ahí, añade por capas métricas Prometheus, trazas OpenTelemetry y monitoreo de errores con Sentry para elevar la observabilidad sin sobrecarga.
- No necesitas añadirlo todo de una vez. Empieza por el área que más duele en tu escala/fase actual y amplía la observabilidad un paso a la vez.
Estoy apoyando discretamente para que tu app FastAPI se vuelva “visible”, y así puedas seguir mejorándola con confianza.
