lunes, 23 de marzo de 2026

Automatizacion con Python - Cap2 - parte 3

Capítulo 02 // Parte 3

Capturar Errores y Problemas

Python Automation Cookbook — Logging y Manejo de Excepciones

La característica principal de una tarea automatizada es su cualidad de "disparar y olvidar". No estamos mirando activamente el resultado, sino haciéndola ejecutarse en segundo plano.

La mayoría de las recetas en este libro tratan con información externa, como páginas web u otros informes, por lo que la probabilidad de encontrar un problema inesperado al ejecutarla es alta. Esta receta presentará una tarea automatizada que almacenará de forma segura comportamientos inesperados en un archivo de registro que puede verificarse después.

PROBLEMA

Sin Manejo de Errores

task_with_error_handling_step1.py
import argparse
import sys

def main(number, other_number, output):
    result = number / other_number  # ¡División!
    print(f'The result is {result}', file=output)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-n1', type=int, help='A number', default=1)
    parser.add_argument('-n2', type=int, help='Another number', default=1)
    parser.add_argument('-o', dest='output', type=argparse.FileType('w'),
                        help='output file', default=sys.stdout)
    
    args = parser.parse_args()
    main(args.n1, args.n2, args.output)

Cuando Falla...

$ python task_with_error_handling_step1.py -n1 5 -n2 0
Traceback (most recent call last):
File "task_with_error_handling_step1.py", line 20, in <module>
main(args.n1, args.n2, args.output)
File "task_with_error_handling_step1.py", line 6, in main
result = number / other_number
ZeroDivisionError: division by zero
El error no se registra en ningún archivo. Si esta tarea se ejecuta automáticamente, no tendremos idea de qué salió mal.
SOLUCIÓN

Con Manejo de Errores y Logging

task_with_error_handling_step4.py
import argparse
import sys
import logging

# Configuración del logging
LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(message)s'
LOG_LEVEL = logging.DEBUG

def main(number, other_number, output):
    logging.info(f'Dividing {number} between {other_number}')
    result = number / other_number
    print(f'The result is {result}', file=output)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-n1', type=int, help='A number', default=1)
    parser.add_argument('-n2', type=int, help='Another number', default=1)
    parser.add_argument('-o', dest='output', type=argparse.FileType('w'),
                        help='output file', default=sys.stdout)
    parser.add_argument('-l', dest='log', type=str, help='log file',
                        default=None)
    
    args = parser.parse_args()
    
    if args.log:
        logging.basicConfig(format=LOG_FORMAT, filename=args.log,
                            level=LOG_LEVEL)
    else:
        logging.basicConfig(format=LOG_FORMAT, level=LOG_LEVEL)
    
    try:
        main(args.n1, args.n2, args.output)
    except Exception as exc:
        logging.exception("Error running task")
        exit(1)

Ejecución con Logging

$ python3 task_with_error_handling_step4.py -n1 5 -n2 0 -l error.log
$ cat error.log
2020-01-19 14:26:15,376 root INFO Dividing 5 between 0
2020-01-19 14:26:15,376 root ERROR division by zero
Traceback (most recent call last):
File "task_with_error_handling_step4.py", line 33, in <module>
main(args.n1, args.n2, args.output)
File "task_with_error_handling_step4.py", line 11, in main
result = number / other_number
ZeroDivisionError: division by zero
ANÁLISIS

Cómo Funciona

Para capturar correctamente cualquier excepción inesperada, la función main debe estar envuelta en un bloque try-except:

patrón try-except
try:
    main(...)
except Exception as exc:
    # Algo salió mal
    logging.exception("Error running task")
    exit(1)
Este tipo de excepción tiene el apodo de "Excepción Pokémon" porque puede atraparlas todas. Capturará cualquier error inesperado en el nivel más alto. No la uses en otras áreas del código, ya que capturar todo puede ocultar errores inesperados.

Niveles de Log

Los niveles disponibles para registros son—de menos crítico a más crítico:

Nivel Uso
DEBUG Información detallada para diagnóstico
INFO Confirmación de que las cosas funcionan como se espera
WARNING Algo inesperado ocurrió, pero el software sigue funcionando
ERROR Debido a un problema, el software no pudo realizar una función
CRITICAL Error grave, el programa puede no poder continuar

Ejemplo de Uso

python shell
>>> import logging
>>> logging.basicConfig(level=logging.INFO)
>>> logging.warning('a warning message')
WARNING:root:a warning message
>>> logging.info('an info message')
INFO:root:an info message
>>> logging.debug('a debug message')
# No se muestra porque el nivel es INFO
AVANZADO

Capacidades del Módulo Logging

  • Formato personalizado: Incluir archivo y número de línea del log
  • Múltiples loggers: Cada uno con su propia configuración
  • Múltiples destinos: Salida estándar, archivo, o logger remoto
  • Rotación automática: Crear nuevos archivos de log por tiempo o tamaño
  • Configuración desde archivos: Leer configuraciones estándar
logging.exception() es un caso especial que crea un registro ERROR, pero también incluye información sobre la excepción, como el stack trace. Úsalo dentro de bloques except.

Para detalles completos, consulta la documentación de Python:

Basado en "Python Automation Cookbook" // Jaime Buelta

Traducción al español — Capítulo 2, Parte 3 de 4