Le scénario d'erreur qui m'a fait repenser ma stratégie de logging

Il est 3h47 du matin quand mon téléphone vibre. Un client me contacte en urgence : l'application ne répond plus. Je plonge dans les logs et découvre un chaos absolu. Des erreurs ConnectionError: timeout after 30s s'accumulent depuis 2 heures. Certaines requêtes retournent un 401 Unauthorized sans raison apparente. D'autres échouent avec des codes 429 Too Many Requests que je n'avais jamais observés en journée. Mais le pire ? Je n'arrive pas à reconstruire l'historique complet des échanges. Certaines réponses sont manquantes, d'autres tronquées. Impossible de reproduire le problème pour le client. Cette nuit-là, j'ai compris l'importance critique d'un système robuste de stockage des logs API.

Pourquoi le logging des requêtes API est indispensable

Chaque échange avec une API IA génère des données précieuses : la requête envoyée, la réponse reçue, les métadonnées temporelles, les codes d'erreur, les latences mesurées. Sans stockage structuré, ces informations s'évaporent dans les logs applicatifs génériques, deviennent inexploitables pour l'analyse, et disparaissent après rotation. Un système de logging efficace vous permet de :

Architecture de référence pour le stockage des logs API

Composants essentiels

Une solution complète de logging comprend généralement :

Implémentation pratique avec PostgreSQL

Commençons par l'implémentation la plus directe : utiliser PostgreSQL comme stockage principal. Cette solution offre d'excellentes performances, une integridad référentielle complète, et des capacités SQL puissantes pour l'analyse.
-- Structure de la table pour stocker les logs de requêtes API
CREATE TABLE api_request_logs (
    id BIGSERIAL PRIMARY KEY,
    request_id UUID NOT NULL DEFAULT gen_random_uuid(),
    endpoint VARCHAR(500) NOT NULL,
    method VARCHAR(10) NOT NULL,
    request_headers JSONB,
    request_body JSONB,
    request_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    response_status_code INTEGER,
    response_headers JSONB,
    response_body JSONB,
    response_timestamp TIMESTAMPTZ,
    latency_ms INTEGER,
    error_message TEXT,
    error_type VARCHAR(100),
    api_provider VARCHAR(50), -- 'holysheep', 'openai', 'anthropic', etc.
    model_used VARCHAR(100),
    tokens_used INTEGER,
    cost_usd DECIMAL(10, 6),
    user_id VARCHAR(100),
    metadata JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Index pour les requêtes fréquentes
CREATE INDEX idx_api_logs_request_id ON api_request_logs(request_id);
CREATE INDEX idx_api_logs_timestamp ON api_request_logs(request_timestamp DESC);
CREATE INDEX idx_api_logs_endpoint ON api_request_logs(endpoint);
CREATE INDEX idx_api_logs_status ON api_request_logs(response_status_code);
CREATE INDEX idx_api_logs_provider ON api_request_logs(api_provider);

-- Partitionnement par mois pour les grandes volumétries
CREATE TABLE api_request_logs_2026_01 PARTITION OF api_request_logs
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

Middleware Python pour capturer tous les échanges

import httpx
import json
import uuid
import time
import psycopg2
from psycopg2.extras import Json
from datetime import datetime, timezone
from contextlib import asynccontextmanager

class APILoggingMiddleware:
    """Middleware pour capturer et stocker les logs de requêtes API."""
    
    def __init__(self, db_connection_string: str):
        self.conn = psycopg2.connect(db_connection_string)
        self.conn.autocommit = True
    
    def _store_log(self, log_data: dict):
        """Insère un log en base de données de façon asynchrone."""
        cursor = self.conn.cursor()
        cursor.execute("""
            INSERT INTO api_request_logs (
                request_id, endpoint, method, request_headers, 
                request_body, request_timestamp, response_status_code,
                response_headers, response_body, response_timestamp,
                latency_ms, error_message, error_type, api_provider,
                model_used, tokens_used, cost_usd, user_id, metadata
            ) VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
            )
        """, (
            log_data.get('request_id'),
            log_data.get('endpoint'),
            log_data.get('method'),
            Json(log_data.get('request_headers')),
            Json(log_data.get('request_body')),
            log_data.get('request_timestamp'),
            log_data.get('response_status_code'),
            Json(log_data.get('response_headers')),
            Json(log_data.get('response_body')),
            log_data.get('response_timestamp'),
            log_data.get('latency_ms'),
            log_data.get('error_message'),
            log_data.get('error_type'),
            log_data.get('api_provider'),
            log_data.get('model_used'),
            log_data.get('tokens_used'),
            log_data.get('cost_usd'),
            log_data.get('user_id'),
            Json(log_data.get('metadata', {}))
        ))
    
    @asynccontextmanager
    async def request(self, method: str, url: str, **kwargs):
        """Contexte asynchrone pour capturer une requête API."""
        request_id = str(uuid.uuid4())
        start_time = time.perf_counter()
        request_timestamp = datetime.now(timezone.utc)
        
        # Extraire les métadonnées de la requête
        request_headers = kwargs.get('headers', {})
        request_body = kwargs.get('json') or kwargs.get('data')
        
        # Déterminer le provider API
        api_provider = self._detect_provider(url)
        
        log_data = {
            'request_id': request_id,
            'endpoint': url,
            'method': method,
            'request_headers': request_headers,
            'request_body': self._sanitize_request(request_body),
            'request_timestamp': request_timestamp,
            'api_provider': api_provider
        }
        
        try:
            async with httpx.AsyncClient(timeout=60.0) as client:
                response = await client.request(method, url, **kwargs)
            
            end_time = time.perf_counter()
            latency_ms = int((end_time - start_time) * 1000)
            
            log_data.update({
                'response_status_code': response.status_code,
                'response_headers': dict(response.headers),
                'response_body': response.json() if response.text else None,
                'response_timestamp': datetime.now(timezone.utc),
                'latency_ms': latency_ms,
                'error_message': None,
                'error_type': None
            })
            
            yield response
            
        except httpx.TimeoutException as e:
            log_data.update({
                'response_status_code': 504,
                'response_timestamp': datetime.now(timezone.utc),
                'latency_ms': int((time.perf_counter() - start_time) * 1000),
                'error_message': str(e),
                'error_type': 'TimeoutException'
            })
            yield None
            
        except httpx.HTTPStatusError as e:
            log_data.update({
                'response_status_code': e.response.status_code,
                'response_timestamp': datetime.now(timezone.utc),
                'latency_ms': int((time.perf_counter() - start_time) * 1000),
                'error_message': str(e),
                'error_type': 'HTTPStatusError'
            })
            yield None
            
        except Exception as e:
            log_data.update({
                'response_status_code': 500,
                'response_timestamp': datetime.now(timezone.utc),
                'latency_ms': int((time.perf_counter() - start_time) * 1000),
                'error_message': str(e),
                'error_type': type(e).__name__
            })
            yield None
            
        finally:
            # Stockage asynchrone du log
            try:
                self._store_log(log_data)
            except Exception as e:
                print(f"Erreur lors du stockage du log: {e}")
    
    def _detect_provider(self, url: str) -> str:
        """Détecte le provider API à partir de l'URL."""
        if 'holysheep' in url:
            return 'holysheep'
        elif 'openai' in url:
            return 'openai'
        elif 'anthropic' in url:
            return 'anthropic'
        elif 'deepseek' in url:
            return 'deepseek'
        return 'unknown'
    
    def _sanitize_request(self, body):
        """Supprime les données sensibles avant stockage."""
        if isinstance(body, dict):
            sanitized = body.copy()
            sensitive_keys = ['api_key', 'authorization', 'password', 'token', 'secret']
            for key in sensitive_keys:
                if key in sanitized:
                    sanitized[key] = '***REDACTED***'
            return sanitized
        return body

Exemple d'utilisation avec HolySheep AI

Maintenant, voici comment utiliser ce middleware avec l'API HolySheep AI. Avec une latence moyenne de <50ms et des tarifs jusqu'à 85% inférieurs à la concurrence américaine, HolySheep représente une alternative économique et performante pour vos besoins en IA.
import os
from dotenv import load_dotenv

load_dotenv()

Configuration HolySheep

BASE_URL = "https://api.holysheep.ai/v1" API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")

Initialisation du middleware de logging

logging_middleware = APILoggingMiddleware( db_connection_string="postgresql://user:password@localhost:5432/api_logs" ) async def generate_with_logging(prompt: str, model: str = "deepseek-v3.2"): """Exemple d'appel à l'API HolySheep avec logging automatique.""" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = { "model": model, "messages": [ {"role": "user", "content": prompt} ], "temperature": 0.7, "max_tokens": 1000 } async with logging_middleware.request( method="POST", url=f"{BASE_URL}/chat/completions", headers=headers, json=payload, metadata={ "user_id": "user_12345", "session_id": "session_abcde", "feature": "chat_completion" } ) as response: if response: data = response.json() # Ajouter les métriques d'usage au log usage = data.get('usage', {}) print(f"Tokens utilisés: {usage.get('total_tokens', 0)}") print(f"Réponse: {data['choices'][0]['message']['content']}") return data else: print("Erreur lors de l'appel API") return None

Exécution

import asyncio async def main(): result = await generate_with_logging( "Expliquez la différence entre une API REST et GraphQL en 3 points." ) asyncio.run(main())

Comparatif des solutions de stockage

Voici un tableau comparatif des différentes options de stockage pour vos logs API, avec leurs avantages, inconvénients et cas d'utilisation.
Solution Avantages Inconvénients Coût estimatif Latence d'écriture Cas d'utilisation optimal
PostgreSQL SQL puissant, ACID, partitioning natif Volume limité, requiere tuning $50-200/mois 5-15ms Applications中等规模,audit complet
MongoDB Schéma flexible, JSON natif, sharding facile Transactions limitées, require expertise $60-300/mois 3-10ms Logs variables, prototypes rapides
Elasticsearch Recherche full-text, scalability immense Complexité opérationnelle élevée $200-1000/mois 10-30ms Grande volumétrie, analyse en temps réel
ClickHouse Performances analytiques exceptionnelles Updates complexes, courbe d'apprentissage $100-500/mois 1-5ms Analytics haute performance, time-series
S3 + Athena Coût réduit, illimité, serverless Latence query élevée, pas de temps réel $10-100/mois N/A (batch) Archivage,audit long terme,debug occasionnel
InfluxDB Optimisé time-series, compression efficace SQL limité,生态系统较小 $50-250/mois 2-8ms Metrics, monitoring, IoT

Stratégie de rétention et archivage

Une politique de rétention bien pensée optimise les coûts tout en garantissant la disponibilité des données critiques :
-- Politique de rétention recommandée
-- Données complètes: 7 jours (logs détaillés pour debugging)
-- Données agrégées: 30 jours (métriques,stats)
-- Données archivées: 12 mois (S3/Glacier pour compliance)

-- Vue materialisée pour les statistiques agrégées par heure
CREATE MATERIALIZED VIEW api_stats_hourly AS
SELECT 
    date_trunc('hour', request_timestamp) as hour,
    api_provider,
    model_used,
    COUNT(*) as total_requests,
    AVG(latency_ms) as avg_latency_ms,
    PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms) as p95_latency,
    SUM(tokens_used) as total_tokens,
    SUM(cost_usd) as total_cost,
    COUNT(*) FILTER (WHERE response_status_code >= 400) as error_count
FROM api_request_logs
WHERE request_timestamp >= NOW() - INTERVAL '30 days'
GROUP BY 1, 2, 3
WITH DATA;

CREATE UNIQUE INDEX ON api_stats_hourly (hour, api_provider, model_used);

-- Vue pour les statistiques par endpoint
CREATE MATERIALIZED VIEW api_stats_by_endpoint AS
SELECT 
    endpoint,
    method,
    COUNT(*) as total_calls,
    AVG(latency_ms) as avg_latency,
    MAX(latency_ms) as max_latency,
    COUNT(*) FILTER (WHERE response_status_code >= 400) as errors
FROM api_request_logs
WHERE request_timestamp >= NOW() - INTERVAL '7 days'
GROUP BY 1, 2
WITH DATA;

-- Procédure d'archivage vers S3 (exemple PostgreSQL FDW)
CREATE OR REPLACE FUNCTION archive_old_logs()
RETURNS void AS $$
BEGIN
    -- Exporter les logs de plus de 30 jours vers S3
    COPY (
        SELECT row_to_json(t) 
        FROM api_request_logs t 
        WHERE request_timestamp < NOW() - INTERVAL '30 days'
    ) TO '/tmp/api_logs_archive.jsonl';
    
    -- Supprimer après archivage réussi
    DELETE FROM api_request_logs 
    WHERE request_timestamp < NOW() - INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql;

Requêtes analytiques pour optimiser vos coûts

-- 1. Top 10 des prompts les plus coûteux
SELECT 
    LEFT(request_body->'messages'->-1->>'content', 100) as prompt_preview,
    COUNT(*) as call_count,
    SUM(tokens_used) as total_tokens,
    SUM(cost_usd) as total_cost
FROM api_request_logs
WHERE request_timestamp >= NOW() - INTERVAL '7 days'
GROUP BY 1
ORDER BY total_cost DESC
LIMIT 10;

-- 2. Analyse des erreurs par provider
SELECT 
    api_provider,
    error_type,
    COUNT(*) as count,
    MIN(error_message) as sample_error
FROM api_request_logs
WHERE error_type IS NOT NULL
AND request_timestamp >= NOW() - INTERVAL '24 hours'
GROUP BY 1, 2
ORDER BY count DESC;

-- 3. Comparaison des latences par provider et modèle
SELECT 
    api_provider,
    model_used,
    COUNT(*) as total_calls,
    ROUND(AVG(latency_ms)::numeric, 2) as avg_latency,
    ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY latency_ms)) as median_latency,
    ROUND(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY latency_ms)) as p99_latency,
    ROUND(SUM(cost_usd)::numeric, 4) as total_cost
FROM api_request_logs
WHERE request_timestamp >= NOW() - INTERVAL '7 days'
GROUP BY 1, 2
ORDER BY avg_latency DESC;

-- 4. Détection des pics d'utilisation anormaux
SELECT 
    date_trunc('hour', request_timestamp) as hour,
    api_provider,
    COUNT(*) as requests,
    LAG(COUNT(*)) OVER (PARTITION BY api_provider ORDER BY date_trunc('hour', request_timestamp)) as prev_hour_requests,
    CASE 
        WHEN COUNT(*) > LAG(COUNT(*)) OVER (PARTITION BY api_provider ORDER BY date_trunc('hour', request_timestamp)) * 3 
        THEN 'SPIKE DETECTED'
        ELSE 'NORMAL'
    END as status
FROM api_request_logs
WHERE request_timestamp >= NOW() - INTERVAL '48 hours'
GROUP BY 1, 2
HAVING COUNT(*) > LAG(COUNT(*)) OVER (PARTITION BY api_provider ORDER BY date_trunc('hour', request_timestamp)) * 3
   OR COUNT(*) > LAG(COUNT(*)) OVER (PARTITION BY api_provider ORDER BY date_trunc('hour', request_timestamp)) * 3
ORDER BY hour DESC;

Monitoring temps réel avec WebSocket

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi import BackgroundTasks
import asyncio
import json

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
    
    async def broadcast(self, message: dict):
        for connection in self.active_connections:
            try:
                await connection.send_json(message)
            except:
                pass

manager = ConnectionManager()

@app.websocket("/ws/logs")
async def websocket_logs(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            # Recevoir les filtres du client
            data = await websocket.receive_text()
            filters = json.loads(data)
            
            # Envoyer les logs correspondant aux filtres
            async for log in stream_logs(filters):
                await websocket.send_json(log)
                
    except WebSocketDisconnect:
        manager.disconnect(websocket)

async def stream_logs(filters: dict):
    """Stream les logs en temps réel depuis PostgreSQL."""
    conn = await asyncpg.connect(DATABASE_URL)
    
    try:
        while True:
            # Requête poll toutes les 500ms
            rows = await conn.fetch("""
                SELECT * FROM api_request_logs
                WHERE created_at > NOW() - INTERVAL '1 second'
                AND ($1::text IS NULL OR api_provider = $1)
                AND ($2::int IS NULL OR response_status_code >= $2)
                ORDER BY created_at DESC
                LIMIT 50
            """, filters.get('provider'), filters.get('min_status'))
            
            for row in rows:
                yield dict(row)
            
            await asyncio.sleep(0.5)
    finally:
        await conn.close()

@app.post("/api/logs/query")
async def query_logs(
    provider: str = None,
    status_code: int = None,
    start_date: datetime = None,
    end_date: datetime = None,
    limit: int = 100
):
    """API REST pour requêter les logs historiquement."""
    query = """
        SELECT * FROM api_request_logs
        WHERE ($1::text IS NULL OR api_provider = $1)
        AND ($2::int IS NULL OR response_status_code = $2)
        AND ($3::timestamptz IS NULL OR request_timestamp >= $3)
        AND ($4::timestamptz IS NULL OR request_timestamp <= $4)
        ORDER BY request_timestamp DESC
        LIMIT $5
    """
    
    async with pool.acquire() as conn:
        rows = await conn.fetch(query, provider, status_code, start_date, end_date, limit)
        return [dict(row) for row in rows]

Pour qui / Pour qui ce n'est pas fait

✅ Ce guide est fait pour vous si :

❌ Ce guide n'est pas fait pour vous si :

Tarification et ROI

Analysons le retour sur investissement d'un système de logging robuste par rapport aux coûts évités :
Poste de coût Sans logging structuré Avec logging complet Économie/Impact
Temps de debugging 2-4h par incident 15-30min par incident -75% du temps
Coût incident production $500-5000/incident $100-500/incident -90% par incident
Optimisation des prompts Impossible 15-30% tokens économisés $150-500/mois
Détection de fuite API keys Trop tardif En temps réel Inestimable
Infrastructure logging $0 $50-150/mois + $50-150
ROI mensuel estimatif +$400-1000/mois

Pourquoi choisir HolySheep

Après des mois de tests comparatifs, HolySheep AI s'est imposé comme mon provider de référence pour plusieurs raisons concrètes : En intégrant HolySheep via l'inscription ici, vous accédez à une API stable avec un excellent rapport qualité-prix, parfaite pour alimenter votre système de logging tout en minimisant vos coûts d'infrastructure.

Mon expérience personnelle

Il y a 18 mois, j'ai déployé ma première application SaaS basée sur l'IA. Comme beaucoup de débutants, je stockais mes logs dans... des fichiers JSON sur le serveur. Ça marchait, jusqu'au jour où un client m'a contacté à 23h pour un problème que je ne pouvais pas reproduire. Cette nuit-là, j'ai compris une leçon fondamentale : en production, un système de logging robuste n'est pas un luxe, c'est une nécessité absolue. J'ai depuis migré vers PostgreSQL avec partitionnement mensuel, et chaque incident de production se diagnostique désormais en quelques minutes au lieu de heures. Aujourd'hui, avec HolySheep comme provider principal, je bénéficie d'une latence exceptionnelle qui rend mon système de logging quasi-transparent pour l'utilisateur final. Les logs s'écrivent en moins de 10ms, sans impact perceptible sur les temps de réponse.

Erreurs courantes et solutions

Erreur 1 : ConnectionError: timeout after 30s

Symptômes : Les requêtes API échouent avec un timeout, souvent par intermittence. Cause racine : Le provider API est temporairement surchargé ou inaccessible, et votre client n'a pas de retry mechanism. Solution :
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential

class ResilientAPIClient:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.headers = {"Authorization": f"Bearer {api_key}"}
        self.client = httpx.AsyncClient(timeout=30.0)
    
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10)
    )
    async def request_with_retry(self, method: str, endpoint: str, **kwargs):
        """Requête avec retry automatique et backoff exponentiel."""
        url = f"{self.base_url}{endpoint}"
        headers = {**self.headers, **kwargs.get('headers', {})}
        
        try:
            response = await self.client.request(
                method=method,
                url=url,
                headers=headers,
                **kwargs
            )
            response.raise_for_status()
            return response.json()
            
        except httpx.TimeoutException as e:
            # Log et retry automatique
            logging.warning(f"Timeout sur {endpoint}, retry en cours...")
            raise
            
        except httpx.HTTPStatusError as e:
            if e.response.status_code in [429, 500, 502, 503, 504]:
                # Erreurs temporaires : retry
                logging.warning(f"Erreur {e.response.status_code}, retry...")
                raise
            else:
                # Erreurs permanentes : ne pas retry
                raise

Utilisation

client = ResilientAPIClient( base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY" ) result = await client.request_with_retry("POST", "/chat/completions", json=payload)

Erreur 2 : 401 Unauthorized

Symptômes : Toutes les requêtes retournent 401, même avec une clé API valide. Cause racine : La clé API a expiré, a été invalidée, ou le format de l'en-tête Authorization est incorrect. Solution :
import os
from functools import lru_cache

class APIKeyManager:
    """Gestionnaire de clés API avec validation et refresh."""
    
    def __init__(self):
        self._current_key = os.getenv("HOLYSHEEP_API_KEY")
        self._key_valid_until = None
    
    @lru_cache(maxsize=1)
    def get_valid_key(self) -> str:
        """Récupère une clé API valide, avec refresh si nécessaire."""
        if not self._is_key_valid():
            self._refresh_key()
        return self._current_key
    
    def _is_key_valid(self) -> bool:
        """Vérifie si la clé actuelle est toujours valide."""
        if not self._current_key:
            return False
        
        # Test de validation avec un appel léger
        import httpx
        try:
            response = httpx.get(
                "https://api.holysheep.ai/v1/models",
                headers={"Authorization": f"Bearer {self._current_key}"},
                timeout=5.0
            )
            return response.status_code == 200
        except:
            return False
    
    def _refresh_key(self):
        """Récupère une nouvelle clé API."""
        # Logique de refresh selon votre provider
        # Ex: appel à l'API de gestion de compte, rotation de clé, etc.
        new_key = os.environ.get("HOLYSHEEP_API_KEY_REFRESH")
        if new_key:
            self._current_key = new_key
            logging.info("Clé API rafraîchie avec succès")
        else:
            raise ValueError("Impossible de rafraîchir la clé API")

Wrapper pour automatiquement injecter la clé valide

def with_valid_auth(func): """Décorateur pour garantir une authentification valide.""" key_manager = APIKeyManager() async def wrapper(*args, **kwargs): headers = kwargs.get('headers', {}) headers['Authorization'] = f"Bearer {key_manager.get_valid_key()}" kwargs['headers'] = headers return await func(*args, **kwargs) return wrapper

Utilisation

@with_valid_auth async def call_api(endpoint: str, **kwargs): """Appel API avec gestion automatique de l'auth.""" async with httpx.AsyncClient() as client: response = await client.post(endpoint, **kwargs) return response.json()

Erreur 3 : 429 Too Many Requests

Symptômes : Les requêtes échouent temporairement avec 429, surtout aux heures de pointe. Cause racine : Vous dépassez le rate limit du provider ou votre quota mensuel. Solution :
import asyncio
import time
from collections import deque
from dataclasses import dataclass

@dataclass
class RateLimiter:
    """Rate limiter avec queue et backoff intelligent."""
    
    requests_per_minute: int
    requests_per_day: int = None
    
    def __post_init__(self):
        self.minute_buckets = deque(maxlen=60)
        self.day_buckets = deque(maxlen=86400)
        self.lock = asyncio.Lock()
    
    async def acquire(self):
        """Attend et acquiert un slot pour une requête."""
        async with self.lock:
            now = time.time()
            
            # Nettoyage des buckets expirés
            while self.minute_buckets and now - self.minute_buckets[0] > 60:
                self.minute_buckets.popleft()
            
            while self.day_buckets and now - self.day_buckets[0] > 86400:
                self.day_buckets.popleft()
            
            # Vérification des limites
            if len(self.minute_buckets) >= self.requests_per_minute:
                wait_time = 60 - (now - self.minute_buckets[0])
                await asyncio.sleep(wait_time)
                return await self.acquire()  # Recursif après wait
            
            if self.requests_per_day and len(self.day_buckets) >= self.requests_per_day:
                wait_time = 86400 - (now - self.day_buckets[0])
                raise Exception(f"Quota quotidien atteint. Réessayez dans {wait_time/3600:.1f}h")
            
            # Enregistrement de la requête
            self.minute_buckets.append(now)
            self.day_buckets.append(now)

Limiter pour HolySheep (ajustez selon votre plan)

limiter = RateLimiter( requests_per_minute=60, # 60 RPM requests_per_day=100000 # 100k requêtes/jour ) async def throttled_api_call(payload: dict): """Effectue un appel API avec limitation de débit.""" await limiter.acquire() async with httpx.AsyncClient() as client: response = await client