Monitoreo y Mantenimiento
🧭 Navegación:
- Anterior: Programar Tareas Automáticas
- Siguiente: Resumen del Capítulo
👁️ Vigilancia continua: El supervisor del almacén
Imagina que has automatizado completamente tu almacén: los productos se mueven solos, los inventarios se actualizan automáticamente y los pedidos se procesan sin intervención humana. Pero, ¿qué sucede si una máquina falla o un proceso se detiene inesperadamente? Necesitas un supervisor que vigile constantemente que todo funcione correctamente.
El monitoreo y mantenimiento son ese supervisor para tus scripts automatizados, asegurando que sigan funcionando correctamente y alertándote cuando algo va mal.
🤔 ¿Por qué es crucial el monitoreo?
Incluso los mejores scripts pueden fallar por razones inesperadas:
- Cambios externos: APIs que cambian, sitios web que se actualizan
- Problemas de recursos: Memoria insuficiente, disco lleno
- Errores inesperados: Casos extremos no contemplados
- Problemas de red: Interrupciones de conexión
- Actualizaciones del sistema: Cambios en dependencias o el entorno
Sin un buen sistema de monitoreo, podrías no darte cuenta de que tus scripts han dejado de funcionar hasta que sea demasiado tarde.
📊 Estrategias de monitoreo
Existen varias estrategias para monitorear tus scripts automatizados:
1. Logging detallado
El logging es la base de cualquier sistema de monitoreo:
import logging
from datetime import datetime
# Configurar logging con timestamp
log_file = f"script_{datetime.now().strftime('%Y%m%d')}.log"
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Usar logging en puntos clave
logging.info("Script iniciado")
try:
# Operación importante
result = perform_critical_operation()
logging.info(f"Operación completada con resultado: {result}")
except Exception as e:
logging.error(f"Error en operación crítica: {str(e)}")
# Posiblemente enviar notificación
finally:
logging.info("Script finalizado")
2. Notificaciones de errores
Configura tu script para enviar alertas cuando ocurran problemas:
Notificaciones por correo electrónico:
import smtplib
from email.message import EmailMessage
def send_error_email(error_message):
"""Envía un correo electrónico de alerta."""
msg = EmailMessage()
msg.set_content(f"Error en script automatizado:\n\n{error_message}")
msg['Subject'] = 'ALERTA: Error en script automatizado'
msg['From'] = 'tu_correo@gmail.com'
msg['To'] = 'destinatario@example.com'
# Configurar servidor SMTP (ejemplo para Gmail)
server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
server.login("tu_correo@gmail.com", "tu_contraseña_o_token")
server.send_message(msg)
server.quit()
# Usar en el manejo de excepciones
try:
# Código que podría fallar
result = risky_operation()
except Exception as e:
error_msg = f"Error: {str(e)}"
logging.error(error_msg)
send_error_email(error_msg)
Nota de seguridad: Para servicios como Gmail, usa contraseñas de aplicación en lugar de tu contraseña principal.
Notificaciones por servicios de mensajería:
Puedes usar servicios como Telegram, Slack o Discord para recibir notificaciones:
# Ejemplo con Telegram (requiere python-telegram-bot)
from telegram import Bot
def send_telegram_alert(message):
"""Envía una alerta a través de Telegram."""
bot_token = 'tu_token_de_bot'
chat_id = 'tu_chat_id'
bot = Bot(token=bot_token)
bot.send_message(chat_id=chat_id, text=message)
# Usar en caso de error
try:
# Código que podría fallar
process_data()
except Exception as e:
error_msg = f"⚠️ ALERTA: Script falló con error: {str(e)}"
send_telegram_alert(error_msg)
3. Archivos de estado y heartbeats
Una técnica simple pero efectiva es crear “heartbeats” (latidos) que indiquen que tu script sigue funcionando:
def update_heartbeat():
"""Actualiza el archivo de heartbeat con la hora actual."""
with open("heartbeat.txt", "w") as f:
f.write(datetime.now().isoformat())
# Llamar periódicamente durante la ejecución
update_heartbeat()
Luego puedes tener otro script que verifique si el heartbeat está actualizado:
def check_heartbeat(max_age_minutes=60):
"""Verifica si el heartbeat está actualizado."""
try:
with open("heartbeat.txt", "r") as f:
last_beat = datetime.fromisoformat(f.read().strip())
age = datetime.now() - last_beat
if age.total_seconds() > max_age_minutes * 60:
send_alert(f"Script inactivo por {age.total_seconds() / 60:.1f} minutos")
return False
return True
except Exception:
send_alert("No se pudo leer el heartbeat")
return False
4. Servicios de monitoreo
Para scripts críticos, considera usar servicios de monitoreo profesionales:
- Uptime Robot: Monitoreo gratuito de sitios web y endpoints
- Healthchecks.io: Servicio especializado en monitorear tareas cron
- Sentry: Monitoreo de errores en aplicaciones
- Prometheus + Grafana: Solución completa de monitoreo y visualización
Ejemplo con Healthchecks.io:
import requests
def ping_healthcheck(success=True):
"""Notifica a Healthchecks.io sobre el estado del script."""
hc_url = "https://hc-ping.com/tu-uuid-de-healthcheck"
if success:
requests.get(hc_url)
else:
requests.get(f"{hc_url}/fail")
# Al inicio del script
try:
# Código principal
main_process()
# Al finalizar con éxito
ping_healthcheck(success=True)
except Exception as e:
logging.error(f"Error: {str(e)}")
ping_healthcheck(success=False)
raise
🔧 Mantenimiento preventivo
Además del monitoreo, es importante realizar mantenimiento preventivo:
1. Rotación de logs
Evita que los archivos de log crezcan indefinidamente:
import logging
from logging.handlers import RotatingFileHandler
# Configurar rotación de logs (máximo 5 archivos de 1MB cada uno)
handler = RotatingFileHandler(
"app.log",
maxBytes=1_000_000, # 1MB
backupCount=5
)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[handler, logging.StreamHandler()]
)
2. Limpieza de archivos temporales
Implementa rutinas para limpiar archivos temporales:
def cleanup_temp_files(directory, max_age_days=7):
"""Elimina archivos temporales más antiguos que max_age_days."""
now = datetime.now()
count = 0
for filename in os.listdir(directory):
if not filename.endswith('.tmp'):
continue
filepath = os.path.join(directory, filename)
file_time = datetime.fromtimestamp(os.path.getmtime(filepath))
age_days = (now - file_time).days
if age_days > max_age_days:
os.remove(filepath)
count += 1
logging.info(f"Limpieza completada: {count} archivos eliminados")
3. Verificación de dependencias
Verifica periódicamente que las dependencias estén actualizadas:
import pkg_resources
import requests
def check_dependencies():
"""Verifica si hay actualizaciones disponibles para las dependencias."""
outdated = []
for dist in pkg_resources.working_set:
package_name = dist.project_name
current_version = dist.version
try:
# Consultar la última versión en PyPI
response = requests.get(f"https://pypi.org/pypi/{package_name}/json")
if response.status_code == 200:
latest_version = response.json()["info"]["version"]
if latest_version != current_version:
outdated.append(f"{package_name}: {current_version} -> {latest_version}")
except Exception:
pass
if outdated:
message = "Dependencias desactualizadas:\n" + "\n".join(outdated)
logging.warning(message)
# Posiblemente enviar notificación
4. Pruebas periódicas
Implementa pruebas automáticas que verifiquen que todo sigue funcionando:
def run_self_tests():
"""Ejecuta pruebas básicas para verificar la funcionalidad."""
tests_passed = True
# Prueba 1: Verificar conexión a la base de datos
try:
db_connection = connect_to_database()
logging.info("Test de conexión a BD: OK")
except Exception as e:
logging.error(f"Test de conexión a BD: FALLÓ - {str(e)}")
tests_passed = False
# Prueba 2: Verificar acceso a API externa
try:
api_response = call_external_api()
if api_response.status_code == 200:
logging.info("Test de API externa: OK")
else:
logging.error(f"Test de API externa: FALLÓ - Código {api_response.status_code}")
tests_passed = False
except Exception as e:
logging.error(f"Test de API externa: FALLÓ - {str(e)}")
tests_passed = False
return tests_passed
📈 Monitoreo de rendimiento
Para scripts que procesan grandes cantidades de datos o se ejecutan frecuentemente, es útil monitorear el rendimiento:
1. Tiempo de ejecución
import time
def measure_execution_time(func):
"""Decorador para medir el tiempo de ejecución de una función."""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
logging.info(f"Función {func.__name__} ejecutada en {execution_time:.2f} segundos")
return result
return wrapper
@measure_execution_time
def process_large_dataset():
# Procesamiento que podría ser lento
pass
2. Uso de recursos
import psutil
def log_resource_usage():
"""Registra el uso actual de recursos."""
process = psutil.Process()
# Uso de memoria
memory_info = process.memory_info()
memory_mb = memory_info.rss / (1024 * 1024)
# Uso de CPU
cpu_percent = process.cpu_percent(interval=1)
# Uso de disco
disk = psutil.disk_usage('/')
disk_percent = disk.percent
logging.info(f"Recursos: Memoria={memory_mb:.1f}MB, CPU={cpu_percent}%, Disco={disk_percent}%")
🔄 Actualizaciones y mejoras
Mantén tus scripts actualizados y mejorándolos continuamente:
1. Control de versiones
Usa un sistema de control de versiones como Git para rastrear cambios:
# Inicializar repositorio
git init
# Añadir archivos
git add .
# Crear commit inicial
git commit -m "Versión inicial del script de automatización"
# Para cada actualización
git add script.py
git commit -m "Mejora: añadida verificación de errores en la función X"
2. Documentación de cambios
Mantén un archivo CHANGELOG.md para documentar cambios importantes:
# Changelog
## [1.2.0] - 2023-07-15
### Añadido
- Notificaciones por Telegram cuando ocurren errores
- Rotación automática de logs
### Corregido
- Error al procesar archivos con nombres especiales
## [1.1.0] - 2023-06-20
### Añadido
- Soporte para nuevos formatos de archivo
- Mejor manejo de errores
### Cambiado
- Optimizado el procesamiento de archivos grandes
3. Actualizaciones graduales
Cuando implementes cambios importantes:
- Haz una copia de seguridad del script actual
- Prueba exhaustivamente los cambios en un entorno de desarrollo
- Implementa gradualmente en producción
- Monitorea de cerca después de la actualización
- Ten un plan de rollback en caso de problemas
🔍 Comprueba tu comprensión
- ¿Por qué es importante implementar logging en scripts automatizados?
- ¿Qué ventajas ofrece un servicio como Healthchecks.io sobre un sistema de heartbeat casero?
- ¿Cómo podrías detectar si un script está consumiendo demasiada memoria?
- ¿Por qué es importante la rotación de logs?
- ¿Qué estrategias puedes usar para ser notificado inmediatamente si un script falla?
🛠️ Ejercicio práctico
Mejora uno de los scripts de automatización que creamos en el capítulo anterior añadiendo:
- Logging detallado con rotación de archivos
- Un sistema de notificación por correo electrónico o Telegram
- Medición y registro del tiempo de ejecución
- Verificación de recursos disponibles antes de ejecutar operaciones intensivas
- Un archivo de heartbeat que se actualice periódicamente
📝 Resumen
En esta sección, has aprendido:
- La importancia del monitoreo para scripts automatizados
- Diferentes estrategias de monitoreo: logging, notificaciones, heartbeats y servicios profesionales
- Técnicas de mantenimiento preventivo para evitar problemas
- Cómo monitorear el rendimiento de tus scripts
- Buenas prácticas para actualizar y mejorar tus scripts a lo largo del tiempo
Con estos conocimientos, puedes asegurarte de que tus scripts automatizados no solo funcionen correctamente al principio, sino que sigan siendo confiables y eficientes a largo plazo.
En la siguiente sección, haremos un resumen del capítulo y veremos cómo todos estos conceptos se integran para crear sistemas de automatización robustos y profesionales.