SPB Git forge

spb/api-ka

Public

API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com

48commits 1branches 0releases
5.9 MBsize
maindefault branch
19 days agolast push
Python 60.9% HTML 21% TypeScript 7.3% JavaScript 5.2% CSS 4.8% Shell 0.8%
2.8 KB · 104 lines python
Raw Blame History
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