Split bot_detector.py (~1982 lines) into 10 focused modules: - config.py: all configuration constants and optional imports - log.py: logging utilities (log_info, log_decision, append_training_history) - infra.py: ClickHouse client, health check HTTP server, shutdown - browser.py: multifactorial browser identification (5 axes) - scoring.py: drift detection, feature validation, SHAP, clustering - models.py: EIF, Autoencoder, XGBoost model management - preprocessing.py: data preprocessing and feature list definitions - pipeline.py: core semi-supervised scoring loop - cycle.py: main analysis cycle orchestration - __main__.py: entry point with startup banner Update Dockerfile to copy package directory and use python -m bot_detector. All 36 existing tests pass unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
"""Logging et journalisation des décisions IA.
|
|
|
|
Fournit log_info() et log_decision() utilisés par tous les modules.
|
|
"""
|
|
import os
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
from .config import (
|
|
LOG_FILE, LOG_BACKUP_COUNT, TRAINING_HISTORY_FILE,
|
|
CONTAMINATION, ANOMALY_THRESHOLD, MODEL_DIR,
|
|
)
|
|
|
|
# ─── Initialisation des répertoires ─────────────────────────────────────────
|
|
|
|
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
|
|
os.makedirs(MODEL_DIR, exist_ok=True)
|
|
|
|
# ─── Logger principal ───────────────────────────────────────────────────────
|
|
|
|
logger = logging.getLogger('bot_detector')
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
_console_handler = logging.StreamHandler()
|
|
_console_handler.setFormatter(logging.Formatter('[%(asctime)s] %(message)s', '%Y-%m-%d %H:%M:%S'))
|
|
logger.addHandler(_console_handler)
|
|
|
|
_file_handler = RotatingFileHandler(
|
|
LOG_FILE, maxBytes=50 * 1024 * 1024, backupCount=LOG_BACKUP_COUNT, encoding='utf-8'
|
|
)
|
|
_file_handler.setFormatter(logging.Formatter('%(message)s'))
|
|
logger.addHandler(_file_handler)
|
|
|
|
|
|
def log_info(message: str):
|
|
"""Enregistre un message de niveau INFO dans le logger du service."""
|
|
logger.info(message)
|
|
|
|
|
|
def log_decision(event: str, cycle_id: str, model: str = '', row: dict = None):
|
|
"""Enregistre un événement de décision IA au format JSONL dans le fichier de log rotatif.
|
|
|
|
Chaque ligne contient l'horodatage, le cycle_id, l'événement, le modèle,
|
|
la contamination, le seuil et les données supplémentaires de ``row``.
|
|
"""
|
|
entry = {
|
|
'ts': datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
|
|
'cycle_id': cycle_id,
|
|
'event': event,
|
|
'model': model,
|
|
'contamination': CONTAMINATION,
|
|
'threshold': ANOMALY_THRESHOLD,
|
|
}
|
|
if row:
|
|
entry.update(row)
|
|
_file_handler.stream.write(json.dumps(entry, ensure_ascii=False, default=str) + '\n')
|
|
_file_handler.stream.flush()
|
|
|
|
|
|
def append_training_history(entry: dict):
|
|
"""Ajoute une entrée de métadonnées d'entraînement au fichier d'historique JSONL."""
|
|
with open(TRAINING_HISTORY_FILE, 'a', encoding='utf-8') as f:
|
|
f.write(json.dumps(entry, ensure_ascii=False, default=str) + '\n')
|