Files
ja4-platform/services/bot-detector/bot_detector/log.py
toto 1f103392ac refactor(bot-detector): extract monolith into modular package
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>
2026-04-09 01:02:04 +02:00

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')