179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
#!/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 s’exé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()
|