Depuración en Producción
Los errores en producción difieren de los fallos locales: la forma del tráfico, el volumen de datos y la configuración del entorno ocultan problemas que tu portátil nunca ve. Los logs estructurados, las trazas distribuidas y el perfilado seguro en vivo cierran esa brecha sin reinicios arriesgados.
Receta
Tarjeta de referencia rápida - lista para copiar y pegar.
import logging
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
)
log = structlog.get_logger()
def handle_request(request_id: str, trace_id: str, tenant: str) -> None:
structlog.contextvars.bind_contextvars(
request_id=request_id, trace_id=trace_id, tenant=tenant
)
log.info("order_created", order_id="ord_123")# Muestra de pila en vivo (sin reinicio, necesita ptrace o CAP_SYS_PTRACE)
py-spy top --pid $(pgrep -f "gunicorn.*api:app")
py-spy dump --pid <pid> --lines 50Cuándo recurrir a esto:
- Picos en la tasa de errores sin una correlación obvia con un despliegue
- La latencia aumenta pero la CPU parece inactiva (GIL o espera de I/O)
- El problema aparece solo para un inquilino o región
- La reproducción local falla después de igualar la versión de Python y las variables de entorno
Ejemplo de Trabajo
"""prod_debug_helpers.py - kit de herramientas mínimo para correlacionar evidencia de producción."""
from __future__ import annotations
import json
import os
import sys
import time
from contextlib import contextmanager
from dataclasses import dataclass, asdict
import structlog
structlog.configure(
Lo que esto demuestra:
- Los logs JSON llevan
request_id,trace_idy metadatos de despliegue en cada línea - Los gestores de contexto vinculan el ámbito una vez por solicitud en lugar de pasar
kwargspor todas partes - La duración y los reintentos descendentes aparecen en el mismo flujo de logs que los eventos de negocio
- El SHA de despliegue en los logs vincula rápidamente los incidentes con los artefactos de lanzamiento
Inmersión Profunda
Cómo Funciona
- Logging estructurado - Campos clave-valor indexados en Loki, CloudWatch o Datadog; buscar texto plano a escala falla.
- Propagación de trazas - OpenTelemetry inyecta
trace_iden los logs cuando el SDK se configura con un puente de logging. - py-spy - Muestrea pilas de CPython externamente; seguro en workers de Gunicorn/Uvicorn bajo carga.
- Reproducción de solo lectura - Copia cargas útiles anonimizadas de producción y estado de feature flags en staging antes de los cambios de código.
- Línea de tiempo de evidencia - Alinea logs, métricas y anotaciones de despliegue en una ventana UTC.
Lista de Señales de Producción
| Señal | Herramienta | Qué revela |
|---|---|---|
| Picos de errores | Sentry / logs | Trazas de pila, versión, impacto en el usuario |
| Latencia | APM p95/p99 | Trazas lentas, consultas N+1 |
| CPU vs espera | py-spy top | Contención de GIL, bucles intensivos |
| Memoria | Gráficos RSS, tracemalloc | Fugas después del despliegue |
| Deriva de configuración | Diferencia de entorno staging vs prod | Secretos faltantes, tamaño de pool incorrecto |
Notas de Python
# Habilita faulthandler al inicio para cuelgues nativos
import faulthandler
faulthandler.enable()
# Depuración temporal en un solo pod (nunca cometer)
import logging
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)Trampas
- Reiniciar pods antes de capturar logs - Pierdes el estado en memoria y la evidencia de la pila. Solución: Captura logs,
py-spy dumpy métricas de heap primero. - IDs de correlación faltantes - Miles de errores idénticos no se pueden buscar. Solución: Requiere
request_id/trace_iden el middleware antes de que se ejecuten los manejadores. - Depurar con nivel DEBUG globalmente - El volumen de logs explota y se filtran PII. Solución: Sube el nivel en un pod o usa muestreo dinámico.
- Asumir que local == prod - Diferentes
PYTHONHASHSEED, locale o pines de dependencias cambian el comportamiento. Solución: Coincide con el digest de la imagen y el entorno en un entorno de preparación. - Bloquear el bucle de eventos en aplicaciones asíncronas - Las llamadas síncronas a ORM detienen todas las solicitudes; la CPU parece baja. Solución: Comprueba las líneas de tiempo de las trazas en busca de segmentos síncronos largos; mueve las I/O fuera del bucle.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Depurador remoto (debugpy) | Repro de staging confirmada | Tráfico de producción (inseguro, lento) |
| Reproducción desde logs de acceso | Rutas de lectura idempotentes | Escrituras con efectos secundarios |
| Bisect de feature flag | Sospecha de nueva ruta de código | Interrupción solo de infraestructura |
| Volcado de heap (memray) | Crecimiento de memoria durante horas | Bucles intensivos limitados por CPU |
Preguntas Frecuentes
¿Cómo depuro sin acceso SSH?
Usa plataformas de logs, APM y kubectl exec/ecs exec solo en un único pod canario. Prefiere scripts de py-spy y env-print registrados en el repositorio sobre ediciones ad hoc.
¿Debo habilitar Python -X dev en producción?
No. El modo de desarrollo añade comprobaciones y advertencias costosas. Usa staging con -X dev y mantén producción con logging ajustado y sondas de salud.
¿Cuál es la primera consulta en un incidente?
Filtra errores por servicio y git_sha de las últimas dos horas, luego pivota en trace_id de la solicitud exitosa más lenta para contrastar.
¿Cómo rastreo una tarea Celery?
Pasa trace_id en los kwargs de la tarea y vincula structlog contextvars en la clase base de la tarea. Vincula los logs del worker a los logs de la API a través del mismo ID.
¿Cuándo no es suficiente py-spy?
Cuando el proceso está bloqueado en código nativo sin marcos de Python, o durante el inicio antes de que los workers se vinculen. Empareja con eBPF o perfilado nativo de APM.
¿Puedo usar pprint en producción?
Solo detrás de un feature flag y un límite de tasa. Prefiere campos estructurados para que los dashboards puedan agregar.
¿Cómo comparo las configuraciones de staging y producción?
Exporta claves de entorno redactadas (solo nombres) y diferencia tamaños de pool, valores de tiempo de espera y feature flags. Nunca pegues secretos en tickets.
¿Qué pasa con el Python 3.14 con hilos libres (free-threaded)?
Los bloqueos relacionados con GIL disminuyen, pero la I/O y la contención de bloqueos permanecen. py-spy todavía ayuda; observa los puntos críticos de threading.Lock en las métricas.
¿Cuánto tiempo debo mantener el logging de depuración activado?
Minutos para un pod. Revierte inmediatamente después de la captura; documenta los hallazgos en el canal de incidentes.
¿Quién debe ser el responsable de la depuración en producción?
El ingeniero de guardia impulsa la evidencia; el propietario del servicio interpreta la lógica del dominio. No depures en solitario un SEV1 sin un comandante de incidentes.
Relacionado
- Playbooks de Respuesta a Incidentes - roles y severidad de los primeros 15 minutos
- Modos Comunes de Fallo en Producción - patrones de interrupción específicos de Python
- Runbooks de Rollback y Recuperación - estabiliza bajo presión
- Logging Estructurado - configuración de logs JSON
- Trazas Distribuidas - propagación de trazas
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.