#!/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 mysql.connector # important pour cibler les exceptions MySQL import utils_db from utils_mail import envoyer_mail # -------------------- 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).") 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).") 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()