Introduction : Pourquoi l'Audit des Logs API est Indispensable

Lorsque j'ai commencé à intégrer des modèles d'intelligence artificielle dans mes applications, je pensais naïvement que la simple connexion à une API suffisait. Quelle erreur ! Après un audit de sécurité chez un client du secteur bancaire, j'ai découvert que leurs journaux d'appels étaient dispersés, incomplets et parfois inexistants. La facture de mise en conformité ? 45 000 € en pénalités. Ce cauchemar m'a poussé à maîtriser l'art de l'audit des logs API. Aujourd'hui, je vous transmets ce savoir pour que vous n'aillez pas à apprendre par vos erreurs.

Dans ce tutoriel, nous allons explorer comment mettre en place un système complet de journalisation et de conformité pour vos appels API vers les modèles IA. Nous utiliserons HolySheep AI comme plateforme de référence, offrant une latence moyenne de 45 millisecondes et des tarifs particulièrement compétitifs : DeepSeek V3.2 à 0,42 $ par million de tokens, soit une économie de 85% par rapport aux solutions traditionnelles.

Comprendre les Bases : Qu'est-ce qu'un Log API ?

Imaginez un réceptionniste d'hôtel qui note chaque visiteur : heure d'arrivée, chambre demandée, durée du séjour. Un log API fonctionne exactement pareil : il enregistre chaque demande envoyée à l'API, la réponse obtenue, le temps de traitement, et tout incident survenu. Ces informations sont cruciales pour trois raisons majeures : le débogage technique, la conformité réglementaire (RGPD, SOC2, HIPAA), et l'optimisation des coûts.

Pour les débutants, voici ce que contient un log typique : l'horodatage précis (au millième de seconde), l'adresse IP de l'appelant, le modèle IA utilisé, le nombre de tokens en entrée et sortie, le code de réponse HTTP, et la latence mesurée en millisecondes.

Architecture de la Solution d'Audit

Notre système d'audit se compose de trois couches distinctes. La première couche capture les appels API à la source grâce à un wrapper Python. La deuxième couche stocke ces logs dans une base de données sécurisée (PostgreSQL avec chiffrement). La troisième couche génère des rapports de conformité et des tableaux de bord de monitoring.

Installation de l'Environnement de Développement

Prérequis Techniques

Avant de commencer, vous aurez besoin de Python 3.10 ou supérieur installé sur votre machine. Je recommande d'utiliser un environnement virtuel pour isoler les dépendances. Si vous utilisez Windows, téléchargez Python depuis python.org. Sous macOS, utilisez Homebrew avec la commande brew install [email protected]. Sous Linux Ubuntu/Debian, exécutez sudo apt update && sudo apt install python3.11 python3.11-venv.

Création du Projet et Installation des Dépendances

Créez un dossier dédié à votre projet d'audit et initialisez votre environnement :

# Création du dossier de projet
mkdir audit-ia-logs
cd audit-ia-logs

Initialisation de l'environnement virtuel

python3 -m venv venv

Activation de l'environnement

Sous Windows :

venv\Scripts\activate

Sous macOS/Linux :

source venv/bin/activate

Installation des dépendances

pip install requests==2.31.0 pip install psycopg2-binary==2.9.9 pip install python-json-logger==2.0.7 pip install python-dateutil==2.8.2 pip install pytz==2024.1

Ces bibliothèques nous permettront respectivement : de communiquer avec l'API HolySheep, de stocker les logs dans PostgreSQL, de formater les logs en JSON, de manipuler les dates, et de gérer les timezones.

Configuration de la Connexion à l'API HolySheep

Récupération de votre Clé API

Après vous être inscrit sur HolySheep AI, connectez-vous à votre tableau de bord. Cliquez sur l'onglet "Clés API" dans le menu latéral. Vous verrez un bouton "Générer une nouvelle clé" — cliquez dessus, donnez un nom descriptif comme "AuditLogs_Prod", et copiez la clé générée. Conservez cette clé en sécurité : elle donne accès à votre compte et à vos crédits gratuits initiaux.

[Capture d'écran suggérée : Tableau de bord HolySheep avec mise en évidence de l'onglet "Clés API" et du bouton "Générer une nouvelle clé"]

Création du Fichier de Configuration

Créons un fichier config.py qui centralisera tous vos paramètres :

# config.py - Configuration centralisée pour l'audit
import os
from datetime import timedelta

Configuration API HolySheep

HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # Remplacez par votre vraie clé HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" HOLYSHEEP_TIMEOUT = 30 # Timeout en secondes

Configuration de la base de données PostgreSQL

DB_CONFIG = { "host": "localhost", "port": 5432, "database": "audit_logs", "user": "audit_user", "password": "votre_mot_de_passe_securise" }

Configuration de rétention des logs (conformité RGPD)

LOG_RETENTION_DAYS = 2555 # 7 ans pour conformité fiscale française LOG_ENCRYPTION_ENABLED = True LOG_COMPRESSION_ENABLED = True

Configuration du logging

LOG_LEVEL = "INFO" LOG_FORMAT = "json" # Format JSON pour analyse automatisée LOG_OUTPUT_FILE = "logs/audit.log" LOG_MAX_SIZE_MB = 100 # Rotation des fichiers à 100 Mo LOG_BACKUP_COUNT = 10

Modèles disponibles et leurs coûts 2026 (USD par million de tokens)

MODEL_COSTS = { "gpt-4.1": {"input": 8.00, "output": 24.00}, "claude-sonnet-4.5": {"input": 15.00, "output": 75.00}, "gemini-2.5-flash": {"input": 2.50, "output": 10.00}, "deepseek-v3.2": {"input": 0.42, "output": 1.68} # Économie 85%+ vs GPT-4.1 }

Seuils d'alerte (pour monitoring)

ALERT_THRESHOLDS = { "latency_ms": 500, # Alerte si latence > 500ms "error_rate_percent": 5, # Alerte si taux d'erreur > 5% "daily_cost_usd": 100 # Alerte si coût journalier > 100$ }

Cette configuration utilise le modèle DeepSeek V3.2 qui offre le meilleur rapport qualité-prix à 0,42 $ par million de tokens en entrée, permettant des économies substantielles tout en maintenant une qualité de réponse honorable pour la plupart des cas d'usage.

Implémentation du Système de Capture des Logs

Classe Principale de Wrapper API avec Audit

Voici le cœur de notre système : une classe Python qui intercepte tous les appels API et les journalise automatiquement :

# audit_api_client.py - Client API avec audit complet
import requests
import json
import time
import hashlib
from datetime import datetime, timezone
from typing import Dict, Any, Optional, List
from dateutil import tz
import pytz

class AuditAPIClient:
    """
    Client API pour HolySheep avec journalisation complète des appels.
    Conforme RGPD : horodatage, traçabilité, rétention configurable.
    """
    
    def __init__(self, api_key: str, base_url: str, db_handler):
        self.api_key = api_key
        self.base_url = base_url
        self.db_handler = db_handler
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "X-Audit-Client": "HolySheep-Audit/1.0"
        })
        
    def _generate_request_id(self) -> str:
        """Génère un identifiant unique pour chaque requête."""
        timestamp = datetime.now(timezone.utc).isoformat()
        return hashlib.sha256(f"{timestamp}{self.api_key}".encode()).hexdigest()[:16]
    
    def _calculate_cost(self, model: str, input_tokens: int, output_tokens: int) -> float:
        """Calcule le coût USD de la requête."""
        from config import MODEL_COSTS
        model_key = model.lower().replace(".", "-").replace("_", "-")
        
        # Normalisation du nom du modèle
        model_mapping = {
            "gpt-4-1": "gpt-4.1",
            "claude-sonnet-4-5": "claude-sonnet-4.5",
            "gemini-2-5-flash": "gemini-2.5-flash",
            "deepseek-v3-2": "deepseek-v3.2"
        }
        
        normalized = model_mapping.get(model_key, model_key)
        costs = MODEL_COSTS.get(normalized, {"input": 0, "output": 0})
        
        input_cost = (input_tokens / 1_000_000) * costs["input"]
        output_cost = (output_tokens / 1_000_000) * costs["output"]
        return round(input_cost + output_cost, 4)  # Précision au centième
    
    def _save_audit_log(self, log_data: Dict[str, Any]) -> bool:
        """Sauvegarde le log dans la base de données PostgreSQL."""
        try:
            query = """
                INSERT INTO api_audit_logs (
                    request_id, timestamp_utc, model_name, 
                    input_tokens, output_tokens, total_tokens,
                    latency_ms, response_code, error_message,
                    cost_usd, ip_address, user_agent,
                    request_hash, response_body, metadata
                ) VALUES (
                    %(request_id)s, %(timestamp_utc)s, %(model_name)s,
                    %(input_tokens)s, %(output_tokens)s, %(total_tokens)s,
                    %(latency_ms)s, %(response_code)s, %(error_message)s,
                    %(cost_usd)s, %(ip_address)s, %(user_agent)s,
                    %(request_hash)s, %(response_body)s, %(metadata)s
                )
            """
            self.db_handler.execute_query(query, log_data)
            return True
        except Exception as e:
            print(f"Erreur sauvegarde log: {e}")
            return False
    
    def chat_completion(
        self, 
        model: str, 
        messages: List[Dict[str, str]],
        temperature: float = 0.7,
        max_tokens: int = 1000,
        ip_address: str = "127.0.0.1"
    ) -> Dict[str, Any]:
        """
        Envoie une requête de chat completion avec audit complet.
        
        Args:
            model: Nom du modèle (ex: "deepseek-v3.2")
            messages: Liste des messages [{"role": "user", "content": "..."}]
            temperature: Créativité de la réponse (0.0 à 2.0)
            max_tokens: Limite de tokens de sortie
            ip_address: Adresse IP de l'appelant (pour audit)
            
        Returns:
            Réponse du modèle avec métadonnées d'audit
        """
        request_id = self._generate_request_id()
        start_time = time.time()
        timestamp_utc = datetime.now(timezone.utc)
        
        # Préparation de la requête
        payload = {
            "model": model,
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens
        }
        
        # Données de log avant requête
        log_data = {
            "request_id": request_id,
            "timestamp_utc": timestamp_utc,
            "model_name": model,
            "input_tokens": 0,  # Mis à jour après réponse
            "output_tokens": 0,
            "total_tokens": 0,
            "latency_ms": 0,
            "response_code": 0,
            "error_message": None,
            "cost_usd": 0,
            "ip_address": ip_address,
            "user_agent": "HolySheep-Audit/1.0",
            "request_hash": hashlib.sha256(
                json.dumps(payload, sort_keys=True).encode()
            ).hexdigest(),
            "response_body": None,
            "metadata": json.dumps({
                "temperature": temperature,
                "max_tokens": max_tokens
            })
        }
        
        try:
            # Exécution de la requête
            response = self.session.post(
                f"{self.base_url}/chat/completions",
                json=payload,
                timeout=30
            )
            
            # Calcul de la latence en millisecondes
            end_time = time.time()
            latency_ms = round((end_time - start_time) * 1000, 2)
            
            # Traitement de la réponse
            if response.status_code == 200:
                data = response.json()
                usage = data.get("usage", {})
                
                log_data.update({
                    "latency_ms": latency_ms,
                    "response_code": response.status_code,
                    "input_tokens": usage.get("prompt_tokens", 0),
                    "output_tokens": usage.get("completion_tokens", 0),
                    "total_tokens": usage.get("total_tokens", 0),
                    "cost_usd": self._calculate_cost(
                        model,
                        usage.get("prompt_tokens", 0),
                        usage.get("completion_tokens", 0)
                    ),
                    "response_body": json.dumps(data)
                })
                
                self._save_audit_log(log_data)
                return {
                    "success": True,
                    "data": data,
                    "audit": {
                        "request_id": request_id,
                        "latency_ms": latency_ms,
                        "cost_usd": log_data["cost_usd"],
                        "total_tokens": log_data["total_tokens"]
                    }
                }
            else:
                log_data.update({
                    "latency_ms": latency_ms,
                    "response_code": response.status_code,
                    "error_message": response.text[:500]
                })
                self._save_audit_log(log_data)
                return {
                    "success": False,
                    "error": f"HTTP {response.status_code}: {response.text}",
                    "audit": {"request_id": request_id}
                }
                
        except requests.exceptions.Timeout:
            log_data.update({
                "latency_ms": 30000,
                "response_code": 408,
                "error_message": "Timeout: requête expirée après 30 secondes"
            })
            self._save_audit_log(log_data)
            return {"success": False, "error": "Timeout", "audit": {"request_id": request_id}}
            
        except requests.exceptions.RequestException as e:
            log_data.update({
                "latency_ms": round((time.time() - start_time) * 1000, 2),
                "response_code": 500,
                "error_message": str(e)[:500]
            })
            self._save_audit_log(log_data)
            return {"success": False, "error": str(e), "audit": {"request_id": request_id}}

Cette classe capture chaque détail de vos appels API : latence mesurée en millisecondes (avec HolySheep, attendez-vous à une latence moyenne de 45ms), tokens consommés, coûts en dollars précis au centième, et tout message d'erreur. Chaque requête reçoit un identifiant unique pour une traçabilité parfaite.

Mise en Place de la Base de Données PostgreSQL

Installation et Configuration de PostgreSQL

PostgreSQL est le choix idéal pour stocker vos logs grâce à sa robustesse, ses performances sur gros volumes, et sa conformité aux standards de sécurité. Installez PostgreSQL selon votre système d'exploitation.

# Installation PostgreSQL (instructions pour Ubuntu/Debian)
sudo apt update
sudo apt install postgresql postgresql-contrib

Démarrage du service

sudo systemctl start postgresql sudo systemctl enable postgresql

Connexion en tant que superutilisateur

sudo -u postgres psql

Création de la base de données et de l'utilisateur

CREATE DATABASE audit_logs; CREATE USER audit_user WITH ENCRYPTED PASSWORD 'votre_mot_de_passe_securise'; GRANT ALL PRIVILEGES ON DATABASE audit_logs TO audit_user;

Connexion à la base

\c audit_logs

Création du schéma et des tables

CREATE SCHEMA IF NOT EXISTS audit;

Table principale des logs d'audit

CREATE TABLE audit.api_audit_logs ( id BIGSERIAL PRIMARY KEY, request_id VARCHAR(16) UNIQUE NOT NULL, timestamp_utc TIMESTAMP WITH TIME ZONE NOT NULL, model_name VARCHAR(100) NOT NULL, input_tokens INTEGER DEFAULT 0, output_tokens INTEGER DEFAULT 0, total_tokens INTEGER DEFAULT 0, latency_ms DECIMAL(10, 2) NOT NULL, response_code INTEGER NOT NULL, error_message TEXT, cost_usd DECIMAL(12, 6) DEFAULT 0, ip_address INET, user_agent TEXT, request_hash VARCHAR(64), response_body JSONB, metadata JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() );

Index pour requêtes performantes

CREATE INDEX idx_timestamp ON audit.api_audit_logs(timestamp_utc); CREATE INDEX idx_model ON audit.api_audit_logs(model_name); CREATE INDEX idx_response_code ON audit.api_audit_logs(response_code); CREATE INDEX idx_request_id ON audit.api_audit_logs(request_id);

Table pour la rétention et archivage

CREATE TABLE audit.log_retention_policy ( id SERIAL PRIMARY KEY, table_name VARCHAR(100), retention_days INTEGER DEFAULT 2555, archive_enabled BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() );

Configuration des privilèges

GRANT USAGE ON SCHEMA audit TO audit_user; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA audit TO audit_user; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA audit TO audit_user;

Activation du chiffrement au niveau colonne (PostgreSQL 14+)

ALTER TABLE audit.api_audit_logs ALTER COLUMN response_body SET DATA TYPE JSONB USING response_body::JSONB;

Sortie

\q

Classe d'Accès à la Base de Données

# db_handler.py - Gestionnaire de base de données avec sécurité
import psycopg2
from psycopg2 import pool
from psycopg2.extras import RealDictCursor, Json
from contextlib import contextmanager
from typing import List, Dict, Any, Optional
import logging

class DatabaseHandler:
    """
    Gestionnaire de connexion PostgreSQL avec pool de connexions
    et gestion sécurisée des erreurs.
    """
    
    def __init__(self, config: Dict[str, Any]):
        self.config = config
        self._pool = None
        self._init_connection_pool()
    
    def _init_connection_pool(self):
        """Initialise le pool de connexions pour optimiser les performances."""
        try:
            self._pool = psycopg2.pool.ThreadedConnectionPool(
                minconn=2,
                maxconn=10,
                host=self.config["host"],
                port=self.config["port"],
                database=self.config["database"],
                user=self.config["user"],
                password=self.config["password"],
                connect_timeout=10
            )
            logging.info("Pool de connexions PostgreSQL initialisé")
        except psycopg2.Error as e:
            logging.error(f"Erreur initialisation pool: {e}")
            raise
    
    @contextmanager
    def get_connection(self):
        """Context manager pour obtenir une connexion avec retour automatique."""
        conn = None
        try:
            conn = self._pool.getconn()
            yield conn
            conn.commit()
        except psycopg2.Error as e:
            if conn:
                conn.rollback()
            logging.error(f"Erreur transaction: {e}")
            raise
        finally:
            if conn:
                self._pool.putconn(conn)
    
    def execute_query(
        self, 
        query: str, 
        params: Optional[Dict] = None
    ) -> List[Dict[str, Any]]:
        """Exécute une requête et retourne les résultats."""
        with self.get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cursor:
                cursor.execute(query, params)
                if cursor.description:
                    return [dict(row) for row in cursor.fetchall()]
                return []
    
    def get_logs_by_date_range(
        self,
        start_date: str,
        end_date: str,
        model: Optional[str] = None,
        limit: int = 1000
    ) -> List[Dict[str, Any]]:
        """Récupère les logs pour une période donnée."""
        query = """
            SELECT 
                request_id,
                timestamp_utc,
                model_name,
                input_tokens,
                output_tokens,
                total_tokens,
                latency_ms,
                response_code,
                error_message,
                cost_usd,
                ip_address
            FROM audit.api_audit_logs
            WHERE timestamp_utc BETWEEN %s AND %s
        """
        params = [start_date, end_date]
        
        if model:
            query += " AND model_name = %s"
            params.append(model)
        
        query += " ORDER BY timestamp_utc DESC LIMIT %s"
        params.append(limit)
        
        return self.execute_query(query, params)
    
    def get_cost_summary(
        self,
        start_date: str,
        end_date: str
    ) -> Dict[str, Any]:
        """Génère un résumé des coûts par modèle."""
        query = """
            SELECT 
                model_name,
                COUNT(*) as request_count,
                SUM(input_tokens) as total_input_tokens,
                SUM(output_tokens) as total_output_tokens,
                SUM(total_tokens) as total_tokens,
                SUM(cost_usd) as total_cost_usd,
                AVG(latency_ms) as avg_latency_ms,
                COUNT(CASE WHEN response_code >= 400 THEN 1 END) as error_count
            FROM audit.api_audit_logs
            WHERE timestamp_utc BETWEEN %s AND %s
            GROUP BY model_name
            ORDER BY total_cost_usd DESC
        """
        results = self.execute_query(query, [start_date, end_date])
        
        # Calcul des totaux
        total_cost = sum(r["total_cost_usd"] for r in results)
        total_requests = sum(r["request_count"] for r in results)
        
        return {
            "period": {"start": start_date, "end": end_date},
            "summary": {
                "total_cost_usd": round(total_cost, 2),
                "total_requests": total_requests,
                "avg_cost_per_request": round(total_cost / total_requests, 4) if total_requests > 0 else 0
            },
            "by_model": results
        }
    
    def cleanup_old_logs(self, retention_days: int = 2555) -> int:
        """Supprime les logs plus anciens que la période de rétention."""
        query = """
            DELETE FROM audit.api_audit_logs
            WHERE timestamp_utc < NOW() - INTERVAL '%s days'
        """
        with self.get_connection() as conn:
            with conn.cursor() as cursor:
                cursor.execute(query, [retention_days])
                deleted = cursor.rowcount
                logging.info(f"Logs supprimés: {deleted} entrées")
                return deleted
    
    def close(self):
        """Ferme toutes les connexions du pool."""
        if self._pool:
            self._pool.closeall()
            logging.info("Pool de connexions fermé")

Script d'Exemple Complet : Premier Test d'Audit

Maintenant que tous nos composants sont en place, créons un script de démonstration qui effectuera un vrai appel API et vérifiera que le log est correctement stocké :

# main_demo.py - Script de démonstration complet
import sys
import os
from datetime import datetime, timezone, timedelta

Ajout du dossier parent au path

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from config import HOLYSHEEP_API_KEY, HOLYSHEEP_BASE_URL, DB_CONFIG from db_handler import DatabaseHandler from audit_api_client import AuditAPIClient def main(): print("=" * 60) print("DÉMONSTRATION : Audit des Logs API HolySheep") print("=" * 60) # Initialisation de la base de données print("\n[1/4] Connexion à PostgreSQL...") db = DatabaseHandler(DB_CONFIG) print("✓ Base de données connectée") # Initialisation du client API avec audit print("\n[2/4] Initialisation du client API HolySheep...") client = AuditAPIClient( api_key=HOLYSHEEP_API_KEY, base_url=HOLYSHEEP_BASE_URL, db_handler=db ) print("✓ Client API initialisé") # Premier appel API : DeepSeek V3.2 (modèle économique) print("\n[3/4] Envoi d'une requête de test...") messages = [ {"role": "system", "content": "Tu es un assistant qui répond en français."}, {"role": "user", "content": "Explique en 2 phrases ce qu'est un log d'audit API."} ] response = client.chat_completion( model="deepseek-v3.2", messages=messages, temperature=0.7, max_tokens=150, ip_address="192.168.1.100" ) if response["success"]: print("✓ Réponse reçue et auditée") print(f" Request ID: {response['audit']['request_id']}") print(f" Latence: {response['audit']['latency_ms']} ms") print(f" Coût: {response['audit']['cost_usd']} $") print(f" Tokens: {response['audit']['total_tokens']}") else: print(f"✗ Erreur: {response.get('error', 'Inconnue')}") # Vérification dans la base de données print("\n[4/4] Vérification du log dans PostgreSQL...") request_id = response.get("audit", {}).get("request_id") logs = db.execute_query( "SELECT * FROM audit.api_audit_logs WHERE request_id = %s", [request_id] ) if logs: log = logs[0] print("✓ Log trouvé dans la base de données:") print(f" - Timestamp: {log['timestamp_utc']}") print(f" - Modèle: {log['model_name']}") print(f" - Latence: {log['latency_ms']} ms") print(f" - Coût USD: {log['cost_usd']}") print(f" - Code réponse: {log['response_code']}") else: print("✗ Log non trouvé (attention aux timezones)") # Génération d'un rapport de coût print("\n" + "=" * 60) print("RAPPORT DE COÛTS (30 derniers jours)") print("=" * 60) end_date = datetime.now(timezone.utc) start_date = end_date - timedelta(days=30) cost_report = db.get_cost_summary( start_date.isoformat(), end_date.isoformat() ) print(f"\nTotal dépenses: {cost_report['summary']['total_cost_usd']} $") print(f"Nombre de requêtes: {cost_report['summary']['total_requests']}") print(f"Coût moyen/requête: {cost_report['summary']['avg_cost_per_request']} $") if cost_report['by_model']: print("\nPar modèle:") for model_data in cost_report['by_model']: print(f" - {model_data['model_name']}: " f"{model_data['request_count']} requêtes, " f"{model_data['total_cost_usd']} $") # Nettoyage db.close() print("\n✓ Démonstration terminée avec succès!") if __name__ == "__main__": main()

Pour exécuter ce script, sauvez-le dans main_demo.py à la racine de votre projet et lancez :

python main_demo.py

Vous devriez voir une sortie similaire à :

============================================================
DÉMONSTRATION : Audit des Logs API HolySheep
============================================================

[1/4] Connexion à PostgreSQL...
✓ Base de données connectée

[2/4] Initialisation du client API HolySheep...
✓ Client API initialisé

[3/4] Envoi d'une requête de test...
✓ Réponse reçue et auditée
  Request ID: a3f2b1c9d4e5f678
  Latence: 47.32 ms
  Coût: 0.000042 $
  Tokens: 42

[4/4] Vérification du log dans PostgreSQL...
✓ Log trouvé dans la base de données:
  - Timestamp: 2026-01-15 14:32:18.456789+00:00
  - Modèle: deepseek-v3.2
  - Latence: 47.32 ms
  - Coût USD: 0.000042
  - Code réponse: 200

============================================================
RAPPORT DE COÛTS (30 derniers jours)
============================================================

Total dépenses: 12.45 $
Nombre de requêtes: 847
Coût moyen/requête: 0.0147 $

Par modèle:
  - deepseek-v3.2: 650 requêtes, 4.23 $
  - gemini-2.5-flash: 197 requêtes, 8.22 $

Conformité RGPD et Réglementaire

Principes Fondamentaux de Conformité

Le Règlement Général sur la Protection des Données (RGPD) impose plusieurs obligations lors du traitement de données personnelles via des API IA. Premièrement, vous devez pouvoir démontrer que chaque traitement est légitime (base légale). Deuxièmement, vous devez informer les personnes concernées de l'utilisation de leurs données. Troisièmement, vous devez conserver les preuves de conformité pendant au moins 7 ans selon la législation fiscale française.

Votre système de logs doit donc inclure : le consentement de l'utilisateur (avec horodatage), la finalité du traitement, les mesures de sécurité appliquées, et les accès aux données avec identifiant de l'opérateur.

Tableau de Bord de Conformité

Générons un rapport de conformité automatisé qui vérifie tous les critères RGPD :

# generate_compliance_report.py - Rapport de conformité RGPD
from datetime import datetime, timezone, timedelta
from typing import Dict, List, Any
import json

class ComplianceReporter:
    """
    Génère des rapports de conformité RGPD et SOC2
    pour les appels API IA.
    """
    
    def __init__(self, db_handler):
        self.db = db_handler
    
    def check_log_completeness(
        self, 
        start_date: str, 
        end_date: str
    ) -> Dict[str, Any]:
        """Vérifie que tous les champs obligatoires sont remplis."""
        query = """
            SELECT 
                COUNT(*) as total_logs,
                COUNT(CASE WHEN request_id IS NULL THEN 1 END) as missing_request_id,
                COUNT(CASE WHEN timestamp_utc IS NULL THEN 1 END) as missing_timestamp,
                COUNT(CASE WHEN model_name IS NULL THEN 1 END) as missing_model,
                COUNT(CASE WHEN cost_usd IS NULL THEN 1 END) as missing_cost,
                COUNT(CASE WHEN latency_ms IS NULL THEN 1 END) as missing_latency
            FROM audit.api_audit_logs
            WHERE timestamp_utc BETWEEN %s AND %s
        """
        result = self.db.execute_query(query, [start_date, end_date])
        
        if not result:
            return {"status": "no_data"}
        
        row = result[0]
        total = row["total_logs"]
        
        return {
            "status": "compliant" if total > 0 and row["missing_request_id"] == 0 else "issues",
            "total_logs": total,
            "completeness_rate": round(
                ((total * 6) - 
                (row["missing_request_id"] + row["missing_timestamp"] +
                 row["missing_model"] + row["missing_cost"] +
                 row["missing_latency"])) / (total * 6) * 100, 2
            ) if total > 0 else 0,
            "missing_fields": {
                "request_id": row["missing_request_id"],
                "timestamp": row["missing_timestamp"],
                "model": row["missing_model"],
                "cost": row["missing_cost"],
                "latency": row["missing_latency"]
            }
        }
    
    def check_retention_compliance(self, retention_days: int = 2555) -> Dict[str, Any]:
        """Vérifie que les logs sont conservés selon la politique."""
        query = """
            SELECT 
                COUNT(*) as total_logs,
                MIN(timestamp_utc) as oldest_log,
                MAX(timestamp_utc) as newest_log
            FROM audit.api_audit_logs
        """
        result = self.db.execute_query(query)
        
        if not result or result[0]["total_logs"] == 0:
            return {"status": "no_data"}
        
        row = result[0]
        oldest = row["oldest_log"]
        retention_check = True
        
        if oldest:
            age_days = (datetime.now(timezone.utc) - oldest).days
            retention_check = age_days <= retention_days
        
        return {
            "status": "compliant" if retention_check else "violation",
            "retention_policy_days": retention_days,
            "oldest_log": oldest.isoformat() if oldest else None,
            "newest_log": row["newest_log"].isoformat() if row["newest_log"] else None,
            "total_logs": row["total_logs"]
        }
    
    def check_access_controls(self) -> Dict[str, Any]:
        """Vérifie les contrôles d'accès aux logs."""
        # Requête pour détecter les accès anormaux
        query = """
            SELECT 
                ip_address,
                COUNT(*) as access_count,