API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com
Python 60.9%
HTML 21%
TypeScript 7.3%
JavaScript 5.2%
CSS 4.8%
Shell 0.8%
1# ============================================2# Projet : API-KA3# Fichier : src/utils/logger.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-168# ============================================9"""Logging structuré JSON avec rotation quotidienne + alertes (logs/alerts.log)."""1011from __future__ import annotations1213import datetime14import json15import logging16import socket17from logging.handlers import TimedRotatingFileHandler1819from src.config import get_settings2021_RESERVED_ATTRS = {22 "name",23 "msg",24 "args",25 "levelname",26 "levelno",27 "pathname",28 "filename",29 "module",30 "exc_info",31 "exc_text",32 "stack_info",33 "lineno",34 "funcName",35 "created",36 "msecs",37 "relativeCreated",38 "thread",39 "threadName",40 "processName",41 "process",42 "taskName",43 "message",44 "asctime",45}464748class JsonFormatter(logging.Formatter):49 """Formate chaque enregistrement de log en une ligne JSON."""5051 def format(self, record: logging.LogRecord) -> str:52 entry: dict[str, object] = {53 "timestamp": datetime.datetime.fromtimestamp(54 record.created, tz=datetime.UTC55 ).isoformat(),56 "level": record.levelname,57 "logger": record.name,58 "message": record.getMessage(),59 "node": socket.gethostname(),60 }61 for key, value in record.__dict__.items():62 if key not in _RESERVED_ATTRS and not key.startswith("_"):63 entry[key] = value64 if record.exc_info:65 entry["exception"] = self.formatException(record.exc_info)66 return json.dumps(entry, ensure_ascii=False, default=str)676869def get_logger(name: str = "apika") -> logging.Logger:70 """Retourne un logger JSON avec rotation quotidienne (logs/apika.log)."""71 logger = logging.getLogger(name)72 if logger.handlers:73 return logger7475 settings = get_settings()76 logger.setLevel(logging.INFO)77 logger.propagate = False7879 formatter = JsonFormatter()8081 file_handler = TimedRotatingFileHandler(82 settings.logs_dir / "apika.log",83 when="midnight",84 backupCount=90,85 encoding="utf-8",86 )87 file_handler.setFormatter(formatter)88 logger.addHandler(file_handler)8990 stream_handler = logging.StreamHandler()91 stream_handler.setFormatter(formatter)92 logger.addHandler(stream_handler)9394 return logger959697def alert(message: str) -> None:98 """Écrit une alerte horodatée dans logs/alerts.log et la logge en ERROR."""99 settings = get_settings()100 timestamp = datetime.datetime.now(tz=datetime.UTC).isoformat()101 with open(settings.logs_dir / "alerts.log", "a", encoding="utf-8") as fh:102 fh.write(f"{timestamp} [ALERT] {message}\n")103 get_logger().error(message, extra={"alert": True})104