Files
Gestion_sondes/app/surveillance_releves.py

179 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/home/debian/Gestion_sondes/myenv/bin/python3
# Surveillance de l'arrivée des relevés (par table/site) + SMS d'alerte / retour à la normale
from datetime import datetime, timedelta
from dotenv import load_dotenv
import os
import logging
import sys
import mysql.connector # important pour cibler les exceptions MySQL
import utils_db
from utils_mail import envoyer_mail
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler(sys.stdout)]
)
# Forcer l'encodage UTF-8 du flux si possible (Windows/PyCharm)
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
# -------------------- LOGS --------------------
LOG_DIR = '/home/debian/Gestion_sondes/Logs'
os.makedirs(LOG_DIR, exist_ok=True)
log_filename = os.path.join(LOG_DIR, datetime.now().strftime("surveillance_%Y-%m-%d.log"))
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.FileHandler(log_filename), logging.StreamHandler()]
)
# -------------------- ENV --------------------
load_dotenv('/home/debian/Gestion_sondes/.env')
# -------------------- PARAMETRES --------------------
tables = ['Saclay', 'Meudon']
DELAI_MINUTES = 15
RAPPEL_HEURES = 6
STATE_DIR = '/tmp/surveillance_states'
os.makedirs(STATE_DIR, exist_ok=True)
TABLES_SET = set(tables) # whitelist simple
def _state_file(site: str) -> str:
return os.path.join(STATE_DIR, f'{site}.state')
def should_send_alert(site: str) -> bool:
"""
Retourne True si on doit envoyer une alerte maintenant (première fois ou rappel).
Met à jour le fichier d'état en cas d'envoi.
"""
sf = _state_file(site)
now = datetime.now()
if not os.path.exists(sf):
# première alerte
try:
with open(sf, 'w') as f:
f.write(now.isoformat())
except OSError as e:
logging.warning(f"Impossible d'écrire l'état {sf} : {e} (on alerte quand même).")
return True
try:
with open(sf, 'r') as f:
raw = f.read().strip()
last_alert = datetime.fromisoformat(raw)
except (OSError, ValueError) as e:
# état illisible/corrompu -> on réinitialise et on alerte
logging.warning(f"Etat corrompu pour {site} ({sf}) : {e}. Réinitialisation.")
try:
with open(sf, 'w') as f:
f.write(now.isoformat())
except OSError as e2:
logging.warning(f"Impossible de réécrire l'état {sf} : {e2}")
return True
if now - last_alert >= timedelta(hours=RAPPEL_HEURES):
try:
with open(sf, 'w') as f:
f.write(now.isoformat())
except OSError as e:
logging.warning(f"Impossible de mettre à jour l'état {sf} : {e} (on alerte quand même).")
return True
return False
def clear_state(site: str) -> None:
sf = _state_file(site)
try:
if os.path.exists(sf):
os.remove(sf)
except OSError as e:
logging.warning(f"Impossible de supprimer l'état {sf} : {e}")
def main():
problemes = []
limite = datetime.now() - timedelta(minutes=DELAI_MINUTES)
# 1) Connexion MySQL (une seule fois)
try:
cnx = utils_db.connect_to_mysql()
cursor = cnx.cursor()
except mysql.connector.Error as e:
logging.error(f"MySQL KO : {e}")
envoyer_mail(
"⚠️ ALERTE : Base MySQL inaccessible (surveillance impossible).",
"Connexion MySQL impossible : la surveillance des relevés ne peut pas sexécuter."
)
return
# 2) Surveillance par table (try SQL à l'intérieur de la boucle)
try:
for table in tables:
if table not in TABLES_SET:
logging.warning(f"Table ignorée (non whitelistée) : {table}")
continue
# 2a) Lecture de la dernière date (erreurs SQL gérées finement)
try:
cursor.execute(f"SELECT MAX(Date) FROM `{table}`")
(last_update,) = cursor.fetchone()
except mysql.connector.Error as e:
logging.error(f"Erreur SQL sur {table} : {e}")
# Vous pouvez décider ici si vous voulez un SMS ou seulement un log.
if should_send_alert(table):
envoyer_mail(
f"⚠️ ALERTE : erreur SQL sur {table} (voir logs).",
f"Erreur SQL détectée sur la table {table}. Merci de consulter le fichier log pour le détail."
)
continue
# 2b) Logique métier (hors try SQL)
if (last_update is None) or (last_update < limite):
if should_send_alert(table):
problemes.append(f"{table} (dernier relevé : {last_update})")
logging.warning(f"⚠️ {table} en défaut (dernier relevé : {last_update})")
else:
logging.info(f"{table} déjà signalé, rappel dans {RAPPEL_HEURES}h.")
else:
# Retour à la normale uniquement si on était en défaut (state présent)
if os.path.exists(_state_file(table)):
message = f"{table} : relevés à nouveau reçus (dernier : {last_update}). Situation normale."
envoyer_mail(
f"✅ OK : {table} relevés reçus",
message
)
clear_state(table)
logging.info(f"📩 Retour à la normale envoyé pour {table}.")
else:
logging.info(f"{table} OK (dernier relevé : {last_update})")
finally:
# 3) Nettoyage MySQL
try:
cursor.close()
cnx.close()
except mysql.connector.Error:
pass
# 4) Alerte groupée si besoin
if problemes:
message = f"⚠️ ALERTE : pas de relevés depuis >{DELAI_MINUTES}min :\n" + "\n".join(problemes)
envoyer_mail(
f"⚠️ ALERTE : absence de relevés > {DELAI_MINUTES} min",
message
)
else:
logging.info("👍 Tout est OK, aucun Mail envoyé.")
if __name__ == "__main__":
main()