48 · Logging: niveles, formatos, basicConfig
Reemplaza las llamadas dispersas a print() con logging estructurado y consciente de niveles. Aprende los cinco niveles estándar, configura el root logger con basicConfig, escribe loggers de módulo nombrados y captura trazas completas automáticamente con logger.exception().
El módulo `logging` de Python le asigna a cada mensaje un nivel de severidad (DEBUG → CRITICAL) y te permite ajustar el umbral de verbosidad sin tocar una sola línea del código de la aplicación. Fijas `basicConfig(level=logging.WARNING)` en producción y tus mensajes DEBUG/INFO desaparecen — cero cambios de código, cero salida extra. `print()` no tiene equivalente: es un viaje de ida a stdout sin concepto de severidad, sin timestamps y sin mecanismo para silenciarlo o redirigirlo selectivamente.
Sin esto:
Sin logging estructurado llenas tu codebase de llamadas a `print()` que debes eliminar o comentar manualmente antes de publicar. No hay forma de silenciar la salida de depuración en producción sin un buscar-y-reemplazar en decenas de archivos, y tampoco hay timestamp automático ni captura de trazas.
Por qué logging supera a print
Cuando escribes print("epoch 1 loss=0.42"), esa cadena va a stdout y se queda ahí — no puedes silenciarla sin borrar la línea, no puedes adjuntar un timestamp y no puedes enrutarla a un archivo sin redirigir el proceso completo. El módulo logging resuelve los tres problemas:
- Niveles — cada registro tiene una severidad:
DEBUG,INFO,WARNING,ERRORoCRITICAL. Configuras un umbral una vez; todo lo que esté por debajo se descarta silenciosamente. - Timestamps y metadatos — la cadena de formato puede incluir
%(asctime)s,%(name)s,%(levelname)sy%(message)ssin código extra. - Redirección fácil — envía el mismo log a stderr, un archivo, un socket o un servicio externo añadiendo un handler. Nada en el código de la aplicación cambia.
- Amigable con bibliotecas — una biblioteca bien comportada usa
logging.getLogger(__name__)y nunca llama abasicConfig. La aplicación que usa la biblioteca decide qué hacer con esos registros.
Los cinco niveles (menor → mayor)
| Nivel | Valor | Significado |
|-------|-------|-------------|
| DEBUG | 10 | Diagnóstico detallado — valores de variables, conteos de bucles |
| INFO | 20 | Confirmación de que las cosas funcionan como se esperaba |
| WARNING | 30 | Algo inesperado — la pipeline continúa pero deberías saberlo |
| ERROR | 40 | Un problema grave — una operación falló |
| CRITICAL | 50 | Un error severo — el programa puede no poder continuar |
Fijar basicConfig(level=logging.INFO) significa que INFO, WARNING, ERROR y CRITICAL se emiten; DEBUG se silencia. Subirlo a WARNING silencia también DEBUG e INFO.
Nota sobre Pyodide
El runtime de Python de Pyodide persiste entre clics de "Run" en la misma sesión del navegador — lo que significa que el root logger acumula handlers con cada ejecución de celda. Para mantener la salida de cada celda predecible, las celdas ejecutables comienzan con importlib.reload(logging) que reinicia el logging a un estado limpio.
Python (in browser)
Con `level=logging.DEBUG` los cinco niveles se emiten. Cámbialo a `logging.WARNING` y solo aparecen las tres últimas líneas — no es necesario cambiar nada en el código de la aplicación.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
El campo `%(asctime)s` usa `datefmt` para su formato de reloj. `%(levelname)s` se rellena por la izquierda a 8 caracteres por defecto — útil para la alineación visual en la terminal. Observa que `WARNING` y superior aparecen aunque se fijó `level=INFO`; `DEBUG` sería filtrado.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
`"training.evaluation"` es un *hijo* de `"training"` — tanto el nombre del logger como la jerarquía separada por puntos son visibles en el campo `%(name)s`. Los loggers hijo propagan registros a su padre por defecto, por lo que un solo handler en el root logger captura todo.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
`logger.exception(msg)` es equivalente a `logger.error(msg, exc_info=True)`. Registra al nivel ERROR y adjunta la traza completa de la excepción actual — sin necesitar `import traceback` ni código extra. Observa los marcadores `%` en `logger.info("batch %d", batch_id)`: el logging usa tradicionalmente formato `%` perezoso para que la cadena solo se construya si el registro realmente se va a emitir. Los f-strings también funcionan, pero siempre se evalúan aunque el nivel esté filtrado.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Llamas a `logging.basicConfig(level=logging.WARNING)`. ¿Cuál de las siguientes llamadas producirá realmente salida?
- `logging` es stdlib — `import logging`, sin instalación. Siempre prefiere usarlo sobre `print()` para cualquier cosa que corra más de un script de un solo uso.
- Los cinco niveles de menor a mayor: `DEBUG (10)` → `INFO (20)` → `WARNING (30)` → `ERROR (40)` → `CRITICAL (50)`. El umbral fijado en `basicConfig(level=...)` filtra todo lo que está por debajo.
- `logging.basicConfig(level=..., format='...', datefmt='...')` configura el root logger. Campos de formato clave: `%(asctime)s`, `%(name)s`, `%(levelname)s`, `%(message)s`.
- Cada módulo debe declarar `logger = logging.getLogger(__name__)` al inicio. Los loggers nombrados forman una jerarquía separada por puntos — `"training.evaluation"` es hijo de `"training"`.
- `logger.exception(msg)` dentro de un bloque `except` registra al nivel ERROR y adjunta automáticamente la traza completa — sin necesitar `import traceback`.
Los bucles de entrenamiento registran la pérdida y las métricas por epoch con `logger.info("epoch=%d loss=%.4f", epoch, loss)`. Las advertencias de la pipeline de datos — filas malas omitidas, conteos de NaN, alertas de desequilibrio de clases — pasan por `logger.warning`. `logger.exception` es la forma de capturar batches fallidos sin perder la traza: envuelve el forward pass en `try/except` y llama a `logger.exception("batch %d fallido", batch_id)`. Los scripts de entrenamiento en producción típicamente fijan `level=INFO` y enrutan a un handler de archivo rotativo para que las ejecuciones largas no pierdan su historial.
Si lo quitas: Sin logging estructurado retrocedes a sentencias `print()` que debes eliminar manualmente antes de publicar — o dejar, bloqueando stdout. No hay forma de silenciar la salida de depuración en producción sin modificar el código, sin timestamps automáticos y sin captura de trazas sin llamar manualmente a `traceback.print_exc()` en cada bloque except.