Registro Estructurado
El registro estructurado adjunta contexto de clave-valor a cada línea de registro —típicamente JSON— para que los operadores puedan filtrar por user_id, order_id o trace_id sin necesidad de arqueología con expresiones regulares.
Receta
import structlog
structlog.configure(
processors=[
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
)
log = structlog.get_logger()
log.info("order_created", order_id="ord_1", amount_cents=4200)Cuándo usar esto:
- Todos los servicios de producción en Python detrás de contenedores o systemd
- Plataformas de registro centralizadas (Loki, Datadog, CloudWatch Logs Insights)
- Correlación con trazas y campos de métricas
- Auditorías de seguridad que necesiten campos de actor/acción buscables
Ejemplo de Trabajo
structlog con contextvars para request_id y enlace de middleware de FastAPI.
import uuid
from contextvars import ContextVar
import structlog
from fastapi import FastAPI, Request
request_id_var: ContextVar[str] = ContextVar("request_id", default="-")
def configure_logging() -> None:
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
)
configure_logging()
log = structlog.get_logger()
app = FastAPI()
@app.middleware("http")
async def bind_request_context(request: Request, call_next):
rid = request.headers.get("x-request-id", str(uuid.uuid4()))
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(request_id=rid, path=request.url.path)
response = await call_next(request)
response.headers["X-Request-ID"] = rid
return response
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
log.info("fetch_order", order_id=order_id)
return {"order_id": order_id}Lo que esto demuestra:
bind_contextvarsañade campos a todos los registros en el ámbito de la solicitud.- El middleware propaga o genera
X-Request-ID. - El renderizador JSON produce un objeto analizable por línea.
Inmersión Profunda
Pipeline de Procesadores
| Procesador | Rol |
|---|---|
merge_contextvars | Inyectar contexto de solicitud |
add_log_level | Campo level |
TimeStamper | Marca de tiempo ISO |
JSONRenderer | Serializar |
Integración con stdlib
import logging
structlog.stdlib.recreate_defaults() # puente a la configuración de loggingNotas de Python
# Nunca registres secretos
log.info("token_refreshed", user_id=u.id) # no: token=tokenErrores Comunes
- Mensajes de registro con f-strings - campos no indexados. Solución: argumentos de palabra clave
log.info("event", user_id=id). - Registro de PII sin política - violaciones del GDPR. Solución: permitir campos en lista blanca, hashear IDs de usuario en entornos no productivos.
- Volcados de carga útil enormes - coste y ruido. Solución: registrar tamaño/hash, no el cuerpo completo.
- Combinación de texto plano y JSON - el analizador falla. Solución: solo JSON en producción; consola formateada en desarrollo.
- Falta
trace_id- no se puede saltar a APM. Solución: enlazar el contexto de traza de OpenTelemetry en el procesador.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
stdlib logging Formatter JSON | Dependencias mínimas | Quieres la ergonomía del pipeline de procesadores |
loguru | Scripts y prototipos | El equipo está estandarizado en structlog |
print(JSON) | Depuración rápida | Servicios de producción |
Preguntas Frecuentes
¿`structlog` vs `logging`?
structlog construye eventos estructurados; se integra con los manejadores de stdlib logging para el transporte.
¿Cómo imprimo en formato bonito localmente?
Usa ConsoleRenderer() en la cadena de procesadores de desarrollo; cambia a JSONRenderer en producción a través de variables de entorno.
¿Integración con Django?
El middleware enlaza contextvars; configura structlog en el diccionario LOGGING reemplazando los formateadores.
¿Cómo registro excepciones?
log.exception("payment_failed", order_id=oid) o exc_info=True en el puente de stdlib.
¿Muestreo de registros?
Muestrea el volumen de depuración/trazas a nivel de procesador; nunca muestrees ERROR sin una política explícita.
¿Cómo obtienen contexto los trabajadores?
Enlaza job_id al inicio de la tarea; propaga las cabeceras de traza desde los atributos del mensaje de encolamiento.
¿`CloudWatch Logs Insights`?
Los campos JSON se vuelven consultables - fields @timestamp, order_id | filter level='error'.
¿Coste de rendimiento?
La serialización JSON es barata comparada con la E/S; evita los appenders de red síncronos que bloquean la ruta de solicitud.
¿Cómo se relaciona esto con el análisis de registros?
Las aplicaciones deben emitir JSON de forma ascendente; el análisis de registros de texto es un recurso de último nivel para sistemas heredados.
¿Campos requeridos?
marca de tiempo, nivel, evento/mensaje, nombre del servicio, entorno, request_id/trace_id cuando estén disponibles.
Relacionado
- Conceptos Básicos de Observabilidad - tres pilares
- Trazas Distribuidas -
trace_iden registros - Seguimiento de Errores - Sentry complementa los registros
- Salud y Preparación - registros de acceso mínimos
- Mejores Prácticas de Observabilidad - política de registro
Versiones de Stack: 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+.