# Fichier: python_cheats/cheatsheets/logging.txt
# Cheatsheet Logging Python - Guide Complet


## [LISTE] Sections principales :

[OK] **Introduction** - Pourquoi utiliser logging vs print()
[OK] **Usage basique** - Les 5 niveaux, logger nommé
[OK] **Configuration basicConfig()** - Toutes les options
[OK] **Formatage** - Attributs disponibles, exemples de formats
[OK] **Architecture** - Loggers, Handlers, Formatters, Filters
[OK] **Handlers** - StreamHandler, FileHandler, RotatingFileHandler, etc.
[OK] **Configuration avancée** - Dictionnaire, YAML, JSON, INI
[OK] **Contextes** - Application, module, classe, bibliothèque
[OK] **Techniques avancées** - Variables, exceptions, async, JSON, coloré
[OK] **Filtres** - Personnalisés, throttling, niveau
[OK] **Rotation** - Par taille, par temps, avec compression
[OK] **Applications web** - Flask, Django, FastAPI
[OK] **Multi-threading/processus** - Approches thread-safe
[OK] **Debugging** - Traceback, inspection, logging temporaire
[OK] **Monitoring** - Email, Syslog, DB, HTTP
[OK] **Performance** - Lazy evaluation, async, optimisation
[OK] **Bonnes pratiques** - Structure, niveaux, messages clairs
[OK] **Dépannage** - Solutions aux problèmes courants
[OK] **Exemples complets** - Scripts CLI, API, daemon, tests
[OK] **Intégrations** - SQLAlchemy, Celery, Loguru, Structlog
[OK] **Patterns avancés** - Factory, retry, métriques, circuit breaker
[OK] **Configuration par environnement** - Dev/Staging/Prod
[OK] **Anti-patterns** - Ce qu'il faut éviter
[OK] **Checklist production** - Points à vérifier



[OK] INTRODUCTION AU LOGGING

# Pourquoi utiliser logging plutôt que print() ?
# [OK] Niveaux de sévérité (DEBUG, INFO, WARNING, ERROR, CRITICAL)
# [OK] Contrôle de sortie (console, fichiers, réseau)
# [OK] Formatage personnalisable
# [OK] Filtrage par module/fonction
# [OK] Rotation automatique de fichiers
# [OK] Performance (peut être désactivé)
# [OK] Timestamps automatiques
# [OK] Traçabilité en production


[OK] USAGE BASIQUE

import logging

# === Configuration simple ===

# Configuration basique (console uniquement)
logging.basicConfig()
logging.warning("Ceci est un avertissement")  # Affiche: WARNING:root:Ceci est un avertissement

# Configuration avec niveau
logging.basicConfig(level=logging.DEBUG)
logging.debug("Message debug")
logging.info("Message info")
logging.warning("Message warning")
logging.error("Message erreur")
logging.critical("Message critique")


# === Les 5 niveaux de log ===

# DEBUG (10): Informations détaillées pour diagnostic
logging.debug("Variable x = 42")

# INFO (20): Confirmation que tout fonctionne
logging.info("Connexion établie avec succès")

# WARNING (30): Quelque chose d'inattendu, mais ça fonctionne
logging.warning("Disque presque plein (90%)")

# ERROR (40): Erreur sérieuse, une fonction a échoué
logging.error("Impossible de se connecter à la base de données")

# CRITICAL (50): Erreur grave, programme peut s'arrêter
logging.critical("Système de fichiers corrompu")


# === Logger nommé (recommandé) ===

# Utiliser __name__ pour traçabilité
logger = logging.getLogger(__name__)
logger.info("Ceci vient du module actuel")

# Logger avec nom personnalisé
logger = logging.getLogger('mon_application')
logger.warning("Message depuis mon_application")


[OK] CONFIGURATION AVEC basicConfig()

import logging

# === Options de basicConfig ===

# Configuration complète
logging.basicConfig(
    level=logging.DEBUG,                    # Niveau minimum
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',           # Format date
    filename='app.log',                     # Fichier de sortie
    filemode='a',                           # 'a' = append, 'w' = overwrite
    encoding='utf-8'                        # Encodage (Python 3.9+)
)

# Exemples de configurations

# 1. Console uniquement (par défaut)
logging.basicConfig(
    level=logging.INFO,
    format='%(levelname)s - %(message)s'
)

# 2. Fichier uniquement
logging.basicConfig(
    filename='app.log',
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# 3. Console ET fichier (nécessite handlers, voir plus loin)

# 4. Format détaillé avec module et ligne
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)

# 5. Format simple pour développement
logging.basicConfig(
    level=logging.DEBUG,
    format='%(levelname)s: %(message)s'
)

# 6. Format JSON-like
logging.basicConfig(
    level=logging.INFO,
    format='{"time": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}'
)


# === IMPORTANT: basicConfig() ne s'applique qu'une fois ===

# [OK] BON: Configuration au début du programme
logging.basicConfig(level=logging.DEBUG)
# Reste du code...

# [X] MAUVAIS: Configuration tardive ou multiple
logger.info("Ce message utilise config par défaut")
logging.basicConfig(level=logging.DEBUG)  # Ignoré!


# === Réinitialiser la configuration ===

# Forcer nouvelle configuration
import logging
import importlib

# Méthode 1: Réinitialisation complète
for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)

logging.basicConfig(level=logging.DEBUG)

# Méthode 2: Recharger le module (déconseillé)
importlib.reload(logging)


[OK] FORMATAGE DES MESSAGES

# === Attributs disponibles dans format ===

# %(name)s              - Nom du logger
# %(levelname)s         - Niveau (DEBUG, INFO, etc.)
# %(levelno)s           - Niveau numérique (10, 20, etc.)
# %(pathname)s          - Chemin complet du fichier source
# %(filename)s          - Nom du fichier source
# %(module)s            - Nom du module
# %(lineno)d            - Numéro de ligne
# %(funcName)s          - Nom de la fonction
# %(created)f           - Timestamp création
# %(asctime)s           - Date/heure formatée
# %(msecs)d             - Millisecondes
# %(thread)d            - ID du thread
# %(threadName)s        - Nom du thread
# %(process)d           - ID du processus
# %(message)s           - Message de log


# === Exemples de formats utiles ===

# Format minimal
'%(levelname)s: %(message)s'
# Résultat: INFO: Application démarrée

# Format standard
'%(asctime)s - %(levelname)s - %(message)s'
# Résultat: 2024-01-15 14:30:22 - INFO - Application démarrée

# Format détaillé (debugging)
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s() - %(message)s'
# Résultat: 2024-01-15 14:30:22 - myapp - DEBUG - main.py:42 - process_data() - Traitement démarré

# Format production
'%(asctime)s [%(levelname)8s] [%(process)d] %(name)s - %(message)s'
# Résultat: 2024-01-15 14:30:22 [    INFO] [12345] myapp - Application démarrée

# Format avec thread
'%(asctime)s [%(threadName)-10s] %(levelname)s - %(message)s'
# Résultat: 2024-01-15 14:30:22 [MainThread] INFO - Application démarrée

# Format JSON (pour parsing automatique)
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", "message": "%(message)s"}'

# Format coloré (avec ANSI codes, voir section dédiée)
'\033[92m%(asctime)s\033[0m - %(levelname)s - %(message)s'


# === Format de date personnalisé ===

logging.basicConfig(
    format='%(asctime)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'  # Format ISO
)

# Autres formats de date
datefmt='%d/%m/%Y %H:%M:%S'      # Format français
datefmt='%Y-%m-%dT%H:%M:%S%z'    # Format ISO 8601
datefmt='%b %d %H:%M:%S'         # Format syslog


[OK] LOGGERS, HANDLERS ET FORMATTERS

# === Architecture du logging ===

# Logger: Point d'entrée, émettent les messages
# Handler: Détermine où vont les messages (console, fichier, réseau)
# Formatter: Détermine le format des messages
# Filter: Filtre optionnel pour messages

# Hiérarchie: Logger -> Handler -> Formatter


# === Créer un logger personnalisé ===

import logging

# Créer logger
logger = logging.getLogger('mon_app')
logger.setLevel(logging.DEBUG)

# Créer handler console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# Créer formatter
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Associer formatter au handler
console_handler.setFormatter(formatter)

# Ajouter handler au logger
logger.addHandler(console_handler)

# Utiliser le logger
logger.debug("Ceci n'apparaîtra pas (< INFO)")
logger.info("Ceci apparaîtra")
logger.error("Ceci aussi")


# === Multiples handlers (console + fichier) ===

import logging

logger = logging.getLogger('mon_app')
logger.setLevel(logging.DEBUG)

# Handler 1: Console (INFO et plus)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter('%(levelname)s - %(message)s')
console_handler.setFormatter(console_format)

# Handler 2: Fichier (DEBUG et plus)
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(file_format)

# Ajouter les handlers
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# Maintenant les logs vont dans console ET fichier
logger.debug("Debug dans fichier seulement")
logger.info("Info dans console ET fichier")


# === Types de handlers courants ===

from logging.handlers import (
    RotatingFileHandler,
    TimedRotatingFileHandler,
    SMTPHandler,
    SysLogHandler,
    HTTPHandler,
    SocketHandler
)

# 1. StreamHandler - Console
handler = logging.StreamHandler()

# 2. FileHandler - Fichier simple
handler = logging.FileHandler('app.log')
handler = logging.FileHandler('app.log', mode='a', encoding='utf-8')

# 3. RotatingFileHandler - Rotation par taille
handler = RotatingFileHandler(
    'app.log',
    maxBytes=10*1024*1024,  # 10 MB
    backupCount=5            # Garde 5 fichiers
)
# Créera: app.log, app.log.1, app.log.2, etc.

# 4. TimedRotatingFileHandler - Rotation par temps
handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',         # 'S', 'M', 'H', 'D', 'midnight', 'W0'-'W6'
    interval=1,              # Tous les 1 jour
    backupCount=7            # Garde 7 jours
)
# when='H' : toutes les heures
# when='D' : tous les jours
# when='W0' : tous les lundis
# when='midnight' : à minuit

# 5. SMTPHandler - Email
handler = SMTPHandler(
    mailhost=('smtp.gmail.com', 587),
    fromaddr='app@example.com',
    toaddrs=['admin@example.com'],
    subject='Erreur Application',
    credentials=('user', 'password'),
    secure=()
)
handler.setLevel(logging.ERROR)  # Seulement erreurs

# 6. SysLogHandler - Syslog Unix
handler = SysLogHandler(address='/dev/log')

# 7. HTTPHandler - POST HTTP
handler = HTTPHandler(
    'logs.example.com:8080',
    '/log',
    method='POST'
)

# 8. SocketHandler - Socket réseau
handler = SocketHandler('localhost', 9999)

# 9. NullHandler - Ignore les logs (pour bibliothèques)
handler = logging.NullHandler()


[OK] CONFIGURATION AVANCÉE

# === Configuration complète typique ===

import logging
from logging.handlers import RotatingFileHandler

def setup_logger(name, log_file, level=logging.INFO):
    """Configure un logger avec rotation"""
    
    # Créer logger
    logger = logging.getLogger(name)
    logger.setLevel(level)
    
    # Éviter duplication si déjà configuré
    if logger.handlers:
        return logger
    
    # Format
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # Handler console
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)
    
    # Handler fichier avec rotation
    file_handler = RotatingFileHandler(
        log_file,
        maxBytes=10*1024*1024,  # 10 MB
        backupCount=5
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    
    # Ajouter handlers
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    
    return logger

# Utilisation
logger = setup_logger('mon_app', 'app.log')
logger.info("Application démarrée")


# === Hiérarchie des loggers ===

# Loggers suivent hiérarchie par points
logger_root = logging.getLogger()              # Root logger
logger_app = logging.getLogger('myapp')        # myapp
logger_db = logging.getLogger('myapp.database') # myapp.database
logger_api = logging.getLogger('myapp.api')     # myapp.api

# Les messages propagent vers le haut
# myapp.database -> myapp -> root

# Désactiver propagation
logger_db.propagate = False


# === Configuration par dictionnaire ===

import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    
    'formatters': {
        'standard': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        },
        'detailed': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
        },
    },
    
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'DEBUG',
            'formatter': 'detailed',
            'filename': 'app.log',
            'maxBytes': 10485760,  # 10MB
            'backupCount': 5,
        },
    },
    
    'loggers': {
        'myapp': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False,
        },
        'myapp.database': {
            'level': 'DEBUG',
            'handlers': ['file'],
            'propagate': False,
        },
    },
    
    'root': {
        'level': 'INFO',
        'handlers': ['console'],
    },
}

# Appliquer configuration
logging.config.dictConfig(LOGGING_CONFIG)

# Utiliser
logger = logging.getLogger('myapp')
logger.info("Configuration par dictionnaire")


# === Configuration depuis fichier INI ===

# fichier: logging.ini
"""
[loggers]
keys=root,myapp

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=consoleHandler

[logger_myapp]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=myapp
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('app.log', 'a')

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
"""

# Charger configuration
import logging.config
logging.config.fileConfig('logging.ini')

# Utiliser
logger = logging.getLogger('myapp')


# === Configuration depuis fichier YAML ===

# fichier: logging.yaml
"""
version: 1
disable_existing_loggers: False

formatters:
  standard:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  detailed:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'

handlers:
  console:
    class: logging.StreamHandler
    level: INFO
    formatter: standard
    stream: ext://sys.stdout
  
  file:
    class: logging.handlers.RotatingFileHandler
    level: DEBUG
    formatter: detailed
    filename: app.log
    maxBytes: 10485760
    backupCount: 5

loggers:
  myapp:
    level: DEBUG
    handlers: [console, file]
    propagate: no

root:
  level: INFO
  handlers: [console]
"""

# Charger configuration
import logging.config
import yaml

with open('logging.yaml', 'r') as f:
    config = yaml.safe_load(f)
    logging.config.dictConfig(config)


# === Configuration depuis fichier JSON ===

# fichier: logging.json
"""
{
  "version": 1,
  "disable_existing_loggers": false,
  "formatters": {
    "standard": {
      "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    }
  },
  "handlers": {
    "console": {
      "class": "logging.StreamHandler",
      "level": "INFO",
      "formatter": "standard"
    },
    "file": {
      "class": "logging.FileHandler",
      "level": "DEBUG",
      "formatter": "standard",
      "filename": "app.log"
    }
  },
  "root": {
    "level": "INFO",
    "handlers": ["console", "file"]
  }
}
"""

# Charger configuration
import logging.config
import json

with open('logging.json', 'r') as f:
    config = json.load(f)
    logging.config.dictConfig(config)


[OK] LOGGING DANS DIFFÉRENTS CONTEXTES

# === Logging dans une application ===

# main.py
import logging

def main():
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    logger = logging.getLogger(__name__)
    logger.info("Application démarrée")
    
    # Code...
    
    logger.info("Application terminée")

if __name__ == '__main__':
    main()


# === Script CLI avec logging ===

import logging
import argparse
import sys

def setup_cli_logging(verbose=False, quiet=False):
    """Configure logging pour CLI"""
    if quiet:
        level = logging.ERROR
    elif verbose:
        level = logging.DEBUG
    else:
        level = logging.INFO
    
    logging.basicConfig(
        level=level,
        format='%(levelname)s: %(message)s' if not verbose else
               '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

def main():
    parser = argparse.ArgumentParser(description='Mon script')
    parser.add_argument('-v', '--verbose', action='store_true', help='Mode verbeux')
    parser.add_argument('-q', '--quiet', action='store_true', help='Mode silencieux')
    parser.add_argument('input', help='Fichier d\'entrée')
    
    args = parser.parse_args()
    
    setup_cli_logging(args.verbose, args.quiet)
    logger = logging.getLogger(__name__)
    
    logger.info(f"Traitement de {args.input}")
    
    try:
        # Traitement...
        logger.debug("Détails du traitement")
        logger.info("Traitement terminé")
    except Exception as e:
        logger.error(f"Erreur: {e}")
        sys.exit(1)

if __name__ == '__main__':
    main()


# === API avec logging contextuel ===

from fastapi import FastAPI, Request
from contextvars import ContextVar
import logging
import uuid
import time

# Variables de contexte
request_id_var: ContextVar[str] = ContextVar('request_id', default=None)
user_id_var: ContextVar[str] = ContextVar('user_id', default=None)

class ContextFilter(logging.Filter):
    """Ajoute contexte aux logs"""
    
    def filter(self, record):
        record.request_id = request_id_var.get()
        record.user_id = user_id_var.get()
        return True

# Configuration logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - [%(request_id)s] [user:%(user_id)s] - %(levelname)s - %(message)s'
)

# Ajouter filtre
logger = logging.getLogger()
for handler in logger.handlers:
    handler.addFilter(ContextFilter())

app = FastAPI()

@app.middleware("http")
async def log_requests(request: Request, call_next):
    # Générer request ID
    request_id = str(uuid.uuid4())
    request_id_var.set(request_id)
    
    # Extraire user (exemple)
    user_id = request.headers.get('X-User-ID', 'anonymous')
    user_id_var.set(user_id)
    
    logger = logging.getLogger(__name__)
    start_time = time.time()
    
    logger.info(f"Request started: {request.method} {request.url.path}")
    
    response = await call_next(request)
    
    duration = (time.time() - start_time) * 1000
    logger.info(f"Request completed: {response.status_code} in {duration:.2f}ms")
    
    return response

@app.get("/")
async def root():
    logger = logging.getLogger(__name__)
    logger.debug("Processing root endpoint")
    return {"message": "Hello World"}


# === Bibliothèque avec logging optionnel ===

# mylib/__init__.py
import logging

# Ajouter NullHandler pour éviter warnings
logging.getLogger(__name__).addHandler(logging.NullHandler())

def get_logger():
    """Retourne logger pour la lib"""
    return logging.getLogger(__name__)

# mylib/module.py
from . import get_logger

logger = get_logger()

def my_function():
    logger.debug("Fonction appelée")
    # Code...
    logger.info("Traitement terminé")


# === Service/Daemon avec logging ===

import logging
from logging.handlers import SysLogHandler, RotatingFileHandler
import signal
import sys
import time

class DaemonLogger:
    """Logger pour service/daemon"""
    
    def __init__(self, name, log_file=None):
        self.logger = logging.getLogger(name)
        self.logger.setLevel(logging.DEBUG)
        
        # Format
        formatter = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )
        
        # Syslog
        syslog_handler = SysLogHandler(address='/dev/log')
        syslog_handler.setLevel(logging.INFO)
        syslog_handler.setFormatter(formatter)
        self.logger.addHandler(syslog_handler)
        
        # Fichier avec rotation
        if log_file:
            file_handler = RotatingFileHandler(
                log_file,
                maxBytes=10*1024*1024,
                backupCount=5
            )
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(formatter)
            self.logger.addHandler(file_handler)
    
    def get_logger(self):
        return self.logger

class MyDaemon:
    def __init__(self):
        self.running = False
        logger_wrapper = DaemonLogger('mydaemon', '/var/log/mydaemon.log')
        self.logger = logger_wrapper.get_logger()
        
        # Handlers de signaux
        signal.signal(signal.SIGTERM, self._signal_handler)
        signal.signal(signal.SIGINT, self._signal_handler)
    
    def _signal_handler(self, signum, frame):
        self.logger.info(f"Signal {signum} reçu, arrêt...")
        self.running = False
    
    def run(self):
        self.logger.info("Daemon démarré")
        self.running = True
        
        try:
            while self.running:
                self.logger.debug("Cycle de traitement")
                # Traitement...
                time.sleep(60)  # Toutes les minutes
        except Exception as e:
            self.logger.exception("Erreur fatale")
            sys.exit(1)
        finally:
            self.logger.info("Daemon arrêté")

if __name__ == '__main__':
    daemon = MyDaemon()
    daemon.run()


# === Tests unitaires avec mocking ===

import logging
import unittest
from unittest.mock import patch, MagicMock

class TestLogging(unittest.TestCase):
    
    @patch('mymodule.logger')
    def test_function_logs_info(self, mock_logger):
        """Test que la fonction logue au niveau INFO"""
        from mymodule import my_function
        
        my_function()
        
        # Vérifier que info() a été appelé
        mock_logger.info.assert_called_once()
        
        # Vérifier le message
        args, kwargs = mock_logger.info.call_args
        self.assertIn("expected text", args[0])
    
    def test_error_logging(self):
        """Test logging d'erreur avec capture"""
        with self.assertLogs('mymodule', level='ERROR') as cm:
            from mymodule import function_that_logs_error
            function_that_logs_error()
            
            # Vérifier présence du log
            self.assertEqual(len(cm.output), 1)
            self.assertIn('ERROR', cm.output[0])


[OK] INTÉGRATIONS POPULAIRES

# === Python standard library ===

# Désactiver logs de modules standards
logging.getLogger('urllib3').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING)
logging.getLogger('asyncio').setLevel(logging.WARNING)


# === SQLAlchemy ===

import logging

# Désactiver echo, utiliser logging à la place
from sqlalchemy import create_engine

engine = create_engine('sqlite:///db.sqlite', echo=False)

# Configurer logging SQLAlchemy
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG)


# === Celery ===

from celery import Celery
from celery.utils.log import get_task_logger

app = Celery('myapp')

# Utiliser logger Celery (garde contexte de tâche)
logger = get_task_logger(__name__)

@app.task
def my_task():
    logger.info("Tâche démarrée")
    # Traitement...
    logger.info("Tâche terminée")


# === Loguru (alternative moderne) ===

from loguru import logger

# Configuration simple
logger.add("app.log", rotation="500 MB", retention="10 days", compression="zip")

# Utilisation
logger.debug("Message debug")
logger.info("Message info")
logger.warning("Message warning")
logger.error("Message error")
logger.critical("Message critique")

# Avec contexte
logger.bind(user_id=123).info("Connexion utilisateur")

# Exceptions automatiques
@logger.catch
def my_function():
    1 / 0  # Exception loggée automatiquement


# === Structlog (logs structurés) ===

import structlog

# Configuration
structlog.configure(
    processors=[
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer()
    ]
)

logger = structlog.get_logger()

# Logs structurés
logger.info("user_login", user_id=123, ip="192.168.1.1")
logger.error("db_connection_failed", database="main", retry_count=3)


[OK] PATTERNS AVANCÉS

# === Logger factory pattern ===

import logging
from typing import Optional

class LoggerFactory:
    """Factory pour créer loggers configurés"""
    
    _instances = {}
    
    @classmethod
    def get_logger(
        cls,
        name: str,
        level: int = logging.INFO,
        log_file: Optional[str] = None
    ) -> logging.Logger:
        """Récupère ou crée logger"""
        
        if name in cls._instances:
            return cls._instances[name]
        
        logger = logging.getLogger(name)
        logger.setLevel(level)
        
        # Console handler
        console_handler = logging.StreamHandler()
        console_handler.setLevel(level)
        console_format = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )
        console_handler.setFormatter(console_format)
        logger.addHandler(console_handler)
        
        # File handler si demandé
        if log_file:
            from logging.handlers import RotatingFileHandler
            file_handler = RotatingFileHandler(
                log_file,
                maxBytes=10*1024*1024,
                backupCount=5
            )
            file_handler.setLevel(logging.DEBUG)
            file_handler.setFormatter(console_format)
            logger.addHandler(file_handler)
        
        cls._instances[name] = logger
        return logger

# Utilisation
logger = LoggerFactory.get_logger('myapp', log_file='app.log')


# === Logging avec retry et backoff ===

import logging
import time
from functools import wraps

logger = logging.getLogger(__name__)

def retry_with_logging(max_attempts=3, delay=1, backoff=2):
    """Décorateur pour retry avec logging"""
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempt = 1
            current_delay = delay
            
            while attempt <= max_attempts:
                try:
                    logger.debug(f"{func.__name__}: Tentative {attempt}/{max_attempts}")
                    result = func(*args, **kwargs)
                    
                    if attempt > 1:
                        logger.info(f"{func.__name__}: Succès après {attempt} tentatives")
                    
                    return result
                    
                except Exception as e:
                    logger.warning(
                        f"{func.__name__}: Échec tentative {attempt}/{max_attempts}: {e}"
                    )
                    
                    if attempt == max_attempts:
                        logger.error(f"{func.__name__}: Échec définitif après {max_attempts} tentatives")
                        raise
                    
                    time.sleep(current_delay)
                    current_delay *= backoff
                    attempt += 1
        
        return wrapper
    return decorator

# Utilisation
@retry_with_logging(max_attempts=3, delay=1, backoff=2)
def unreliable_function():
    # Code qui peut échouer
    pass


# === Logging avec métriques ===

import logging
import time
from collections import defaultdict

class MetricsHandler(logging.Handler):
    """Handler qui collecte métriques"""
    
    def __init__(self):
        super().__init__()
        self.metrics = defaultdict(int)
        self.start_time = time.time()
    
    def emit(self, record):
        self.metrics[record.levelname] += 1
        self.metrics['total'] += 1
    
    def get_metrics(self):
        """Retourne métriques"""
        uptime = time.time() - self.start_time
        return {
            'uptime_seconds': uptime,
            'total_logs': self.metrics['total'],
            'debug': self.metrics['DEBUG'],
            'info': self.metrics['INFO'],
            'warning': self.metrics['WARNING'],
            'error': self.metrics['ERROR'],
            'critical': self.metrics['CRITICAL'],
            'logs_per_second': self.metrics['total'] / uptime if uptime > 0 else 0
        }
    
    def reset_metrics(self):
        """Reset métriques"""
        self.metrics.clear()
        self.start_time = time.time()

# Utilisation
metrics_handler = MetricsHandler()
logger = logging.getLogger()
logger.addHandler(metrics_handler)

# Plus tard...
print(metrics_handler.get_metrics())


# === Circuit breaker avec logging ===

import logging
from datetime import datetime, timedelta

logger = logging.getLogger(__name__)

class CircuitBreaker:
    """Circuit breaker avec logging détaillé"""
    
    def __init__(self, failure_threshold=5, timeout=60):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.failures = 0
        self.last_failure_time = None
        self.state = 'CLOSED'  # CLOSED, OPEN, HALF_OPEN
    
    def call(self, func, *args, **kwargs):
        """Appelle fonction avec circuit breaker"""
        
        if self.state == 'OPEN':
            if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
                logger.info("Circuit breaker: Passage OPEN -> HALF_OPEN")
                self.state = 'HALF_OPEN'
            else:
                logger.warning("Circuit breaker: OPEN - Appel bloqué")
                raise Exception("Circuit breaker is OPEN")
        
        try:
            result = func(*args, **kwargs)
            
            if self.state == 'HALF_OPEN':
                logger.info("Circuit breaker: Test réussi, passage HALF_OPEN -> CLOSED")
                self.state = 'CLOSED'
                self.failures = 0
            
            return result
            
        except Exception as e:
            self.failures += 1
            self.last_failure_time = datetime.now()
            
            logger.error(f"Circuit breaker: Échec #{self.failures}: {e}")
            
            if self.failures >= self.failure_threshold:
                logger.critical(
                    f"Circuit breaker: Seuil atteint ({self.failures}/{self.failure_threshold}), "
                    f"passage {self.state} -> OPEN"
                )
                self.state = 'OPEN'
            
            raise


[OK] CONFIGURATION PAR ENVIRONNEMENT

# === Configuration dev/staging/prod ===

import logging
import os

def setup_logging_by_env():
    """Configure logging selon environnement"""
    
    env = os.getenv('ENVIRONMENT', 'development')
    
    if env == 'development':
        # Dev: Verbose, console
        logging.basicConfig(
            level=logging.DEBUG,
            format='%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
            handlers=[logging.StreamHandler()]
        )
        
    elif env == 'staging':
        # Staging: Modéré, console + fichier
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.StreamHandler(),
                logging.handlers.RotatingFileHandler(
                    'staging.log',
                    maxBytes=10*1024*1024,
                    backupCount=5
                )
            ]
        )
        
    elif env == 'production':
        # Production: Minimal, fichier avec rotation, syslog
        from logging.handlers import RotatingFileHandler, SysLogHandler
        
        handlers = [
            RotatingFileHandler(
                '/var/log/myapp/app.log',
                maxBytes=50*1024*1024,
                backupCount=10
            ),
            SysLogHandler(address='/dev/log')
        ]
        
        logging.basicConfig(
            level=logging.WARNING,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=handlers
        )
        
        # Désactiver debug de libs tierces
        logging.getLogger('urllib3').setLevel(logging.ERROR)
        logging.getLogger('requests').setLevel(logging.ERROR)


# === Configuration depuis variables d'environnement ===

import logging
import os

# Lire niveau depuis env
log_level = os.getenv('LOG_LEVEL', 'INFO')
log_file = os.getenv('LOG_FILE', 'app.log')
log_format = os.getenv('LOG_FORMAT', 'standard')

# Map formats
formats = {
    'simple': '%(levelname)s - %(message)s',
    'standard': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    'detailed': '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
    'json': '{"time": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}'
}

logging.basicConfig(
    level=getattr(logging, log_level.upper()),
    format=formats.get(log_format, formats['standard']),
    filename=log_file if log_file != 'console' else None
)


[OK] DEBUGGING DU LOGGING

# === Afficher configuration logging ===

import logging

def print_logging_config():
    """Affiche configuration actuelle du logging"""
    
    print("=== Configuration Logging ===\n")
    
    # Root logger
    root = logging.getLogger()
    print(f"Root Logger:")
    print(f"  Level: {logging.getLevelName(root.level)}")
    print(f"  Handlers: {len(root.handlers)}")
    print(f"  Propagate: {root.propagate}")
    
    for i, handler in enumerate(root.handlers):
        print(f"\n  Handler {i}: {handler.__class__.__name__}")
        print(f"    Level: {logging.getLevelName(handler.level)}")
        if hasattr(handler, 'formatter') and handler.formatter:
            print(f"    Format: {handler.formatter._fmt}")
        if hasattr(handler, 'baseFilename'):
            print(f"    File: {handler.baseFilename}")
    
    # Autres loggers
    print("\n=== Autres Loggers ===")
    for name, logger in logging.Logger.manager.loggerDict.items():
        if isinstance(logger, logging.Logger):
            print(f"\n{name}:")
            print(f"  Level: {logging.getLevelName(logger.level)}")
            print(f"  Handlers: {len(logger.handlers)}")
            print(f"  Propagate: {logger.propagate}")

# Appeler pour debug
print_logging_config()


# === Tester configuration logging ===

import logging

def test_logging():
    """Teste tous les niveaux de logging"""
    
    logger = logging.getLogger(__name__)
    
    print("Test des niveaux de logging:")
    print(f"Logger level: {logging.getLevelName(logger.level)}")
    print(f"Effective level: {logging.getLevelName(logger.getEffectiveLevel())}")
    print()
    
    logger.debug("Test DEBUG")
    logger.info("Test INFO")
    logger.warning("Test WARNING")
    logger.error("Test ERROR")
    logger.critical("Test CRITICAL")
    
    # Test exception
    try:
        1 / 0
    except:
        logger.exception("Test EXCEPTION")

test_logging()


# === Activer logging interne ===

# Debugging du module logging lui-même
import logging

# Activer debug logging interne
logging.basicConfig(level=logging.DEBUG)
logging.debug("Debug activé")

# Ou plus verbeux
import logging.config
logging.config.dictConfig({
    'version': 1,
    'disable_existing_loggers': False,
    'incremental': False,  # Permet de voir warnings de configuration
})


[OK] RESSOURCES ET RÉFÉRENCES

# Documentation officielle:
# https://docs.python.org/3/library/logging.html
# https://docs.python.org/3/library/logging.config.html
# https://docs.python.org/3/howto/logging.html
# https://docs.python.org/3/howto/logging-cookbook.html

# Bibliothèques alternatives/complémentaires:
# - loguru: https://github.com/Delgan/loguru
# - structlog: https://www.structlog.org/
# - python-json-logger: https://github.com/madzak/python-json-logger
# - colorlog: https://github.com/borntyping/python-colorlog
# - rich: https://rich.readthedocs.io/

# Services de logging cloud:
# - AWS CloudWatch Logs
# - Google Cloud Logging
# - Azure Monitor
# - Datadog
# - Loggly
# - Papertrail
# - Sentry (pour erreurs)

# Outils de parsing de logs:
# - Logstash (ELK stack)
# - Fluentd
# - Graylog
# - Splunk

# Best practices:
# - The Twelve-Factor App: https://12factor.net/logs
# - Python Logging HOWTO
# - Real Python Logging Guide


[OK] ANTI-PATTERNS À ÉVITER

# === [X] MAUVAIS: Logger dans boucle serrée ===
for i in range(1000000):
    logger.debug(f"Iteration {i}")  # Trop de logs!

# [OK] BON: Logger seulement si pertinent
for i in range(1000000):
    if i % 10000 == 0:
        logger.debug(f"Progress: {i/10000}%")


# === [X] MAUVAIS: Catch exception sans log ===
try:
    dangerous_operation()
except Exception:
    pass  # Erreur silencieuse!

# [OK] BON: Logger avant de réagir
try:
    dangerous_operation()
except Exception as e:
    logger.error(f"Operation failed: {e}", exc_info=True)
    # Handle error...


# === [X] MAUVAIS: Log et re-log de la même erreur ===
def function_a():
    try:
        function_b()
    except Exception as e:
        logger.error(f"Error in function_a: {e}")
        raise

def function_b():
    try:
        risky_operation()
    except Exception as e:
        logger.error(f"Error in function_b: {e}")  # Doublé!
        raise

# [OK] BON: Logger une seule fois au bon niveau
def function_a():
    try:
        function_b()
    except Exception as e:
        logger.error(f"Error in operation: {e}", exc_info=True)
        # Handle or raise

def function_b():
    # Pas de log, laisse caller gérer
    risky_operation()


# === [X] MAUVAIS: Format string trop tôt ===
logger.debug(f"Expensive: {very_expensive_operation()}")  # Évalué même si DEBUG désactivé!

# [OK] BON: Lazy evaluation
logger.debug("Expensive: %s", very_expensive_operation())  # Évalué seulement si loggé


# === [X] MAUVAIS: Informations sensibles ===
logger.info(f"User login: {username}:{password}")  # MOT DE PASSE!

# [OK] BON: Informations sanitisées
logger.info(f"User login: {username}")


# === [X] MAUVAIS: Logs trop verbeux en production ===
logging.basicConfig(level=logging.DEBUG)  # Tous les détails!

# [OK] BON: Niveau approprié
env = os.getenv('ENVIRONMENT', 'dev')
level = logging.DEBUG if env == 'dev' else logging.INFO
logging.basicConfig(level=level)


[OK] CHECKLIST DE MISE EN PRODUCTION

# [ ] Niveau de log approprié (WARNING ou INFO en prod)
# [ ] Rotation de fichiers configurée
# [ ] Pas d'informations sensibles loggées
# [ ] Handlers correctement configurés
# [ ] Format de log structuré/parseable
# [ ] Logs centralisés (si infrastructure distribuée)
# [ ] Monitoring/alerting sur erreurs configuré
# [ ] Taille des logs sous contrôle
# [ ] Rétention définie
# [ ] Compression activée si nécessaire
# [ ] Performance acceptable (logging asynchrone si besoin)
# [ ] Tests de logging en place
# [ ] Documentation du format de logs
# [ ] Logs de libs tierces filtrés/réduits


# Configuration production recommandée:
logging.basicConfig(
    level=logging.WARNING,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        RotatingFileHandler(
            '/var/log/myapp/app.log',
            maxBytes=50*1024*1024,  # 50MB
            backupCount=10,
            encoding='utf-8'
        )
    ]
)

# Réduire bruit des libs
logging.getLogger('urllib3').setLevel(logging.ERROR)
logging.getLogger('requests').setLevel(logging.ERROR)
logging.getLogger('boto3').setLevel(logging.ERROR)
logging.getLogger('botocore').setLevel(logging.ERROR)


# === Logging dans un module ===

# mymodule.py
import logging

# Créer logger au niveau module
logger = logging.getLogger(__name__)

def my_function():
    logger.debug("Fonction appelée")
    try:
        # Code...
        logger.info("Traitement réussi")
    except Exception as e:
        logger.error(f"Erreur: {e}", exc_info=True)


# === Logging dans une classe ===

import logging

class MyClass:
    def __init__(self):
        self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
        self.logger.info("Instance créée")
    
    def process(self):
        self.logger.debug("Début traitement")
        try:
            # Code...
            self.logger.info("Traitement terminé")
        except Exception as e:
            self.logger.error("Erreur traitement", exc_info=True)


# === Logging dans bibliothèque/package ===

# mylib/__init__.py
import logging

# Utiliser NullHandler pour éviter "No handlers" warning
logging.getLogger(__name__).addHandler(logging.NullHandler())

# L'utilisateur de la lib configure le logging comme il veut


# === Logging avec décorateur ===

import logging
from functools import wraps

def logged(func):
    """Décorateur pour logger entrée/sortie fonction"""
    logger = logging.getLogger(func.__module__)
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        logger.debug(f"Appel {func.__name__}({args}, {kwargs})")
        try:
            result = func(*args, **kwargs)
            logger.debug(f"{func.__name__} retourne {result}")
            return result
        except Exception as e:
            logger.error(f"Erreur dans {func.__name__}: {e}", exc_info=True)
            raise
    
    return wrapper

# Utilisation
@logged
def calculate(x, y):
    return x + y


# === Logging avec context manager ===

import logging
from contextlib import contextmanager

@contextmanager
def log_context(logger, message, level=logging.INFO):
    """Context manager pour logger début/fin opération"""
    logger.log(level, f"Début: {message}")
    try:
        yield
    except Exception as e:
        logger.error(f"Erreur dans {message}: {e}", exc_info=True)
        raise
    finally:
        logger.log(level, f"Fin: {message}")

# Utilisation
logger = logging.getLogger(__name__)

with log_context(logger, "Traitement fichier"):
    # Code...
    pass


[OK] TECHNIQUES AVANCÉES

# === Logging avec variables ===

logger = logging.getLogger(__name__)

# Méthode 1: f-string (évaluation immédiate)
x = 42
logger.info(f"Valeur de x: {x}")

# Méthode 2: % formatting (évaluation lazy)
logger.info("Valeur de x: %d", x)

# Méthode 3: .format (évaluation immédiate)
logger.info("Valeur de x: {}".format(x))

# RECOMMANDÉ: % formatting pour performance
# Le formatage n'est fait que si le message est loggé
logger.debug("Variable complexe: %s", expensive_calculation())  # Pas appelé si DEBUG désactivé


# === Logging d'exceptions ===

logger = logging.getLogger(__name__)

try:
    result = 10 / 0
except Exception as e:
    # Méthode 1: Message simple
    logger.error(f"Erreur: {e}")
    
    # Méthode 2: Avec traceback (recommandé)
    logger.error("Erreur de division", exc_info=True)
    
    # Méthode 3: Avec exception() - équivalent à error() avec exc_info=True
    logger.exception("Erreur de division")
    
    # Méthode 4: Avec traceback personnalisé
    import traceback
    logger.error("Erreur: %s\n%s", e, traceback.format_exc())


# === Logging asynchrone ===

from logging.handlers import QueueHandler, QueueListener
import logging
import queue
import threading

# Créer queue
log_queue = queue.Queue()

# Handler qui met dans queue
queue_handler = QueueHandler(log_queue)

# Handler réel (fichier)
file_handler = logging.FileHandler('app.log')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)

# Listener qui vide la queue
listener = QueueListener(log_queue, file_handler)
listener.start()

# Configuration logger
logger = logging.getLogger()
logger.addHandler(queue_handler)
logger.setLevel(logging.DEBUG)

# Utilisation normale
logger.info("Message asynchrone")

# Arrêter listener à la fin
listener.stop()


# === Logging structuré (JSON) ===

import logging
import json

class JSONFormatter(logging.Formatter):
    """Formatter qui produit du JSON"""
    
    def format(self, record):
        log_data = {
            'timestamp': self.formatTime(record, self.datefmt),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno,
        }
        
        # Ajouter exception si présente
        if record.exc_info:
            log_data['exception'] = self.formatException(record.exc_info)
        
        # Ajouter attributs personnalisés
        if hasattr(record, 'user_id'):
            log_data['user_id'] = record.user_id
        
        return json.dumps(log_data)

# Utilisation
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)

# Logger avec attributs personnalisés
logger.info("Utilisateur connecté", extra={'user_id': 123})


# === Logging avec contexte (contextvars) ===

import logging
from contextvars import ContextVar

# Variable de contexte
request_id_var = ContextVar('request_id', default=None)

class ContextFilter(logging.Filter):
    """Filtre qui ajoute request_id au log"""
    
    def filter(self, record):
        record.request_id = request_id_var.get()
        return True

# Configuration
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.addFilter(ContextFilter())
handler.setFormatter(logging.Formatter(
    '%(asctime)s - [%(request_id)s] - %(levelname)s - %(message)s'
))
logger.addHandler(handler)

# Utilisation
request_id_var.set('req-123')
logger.info("Traitement requête")  # Affiche [req-123]


# === Logging coloré (terminal) ===

import logging

class ColoredFormatter(logging.Formatter):
    """Formatter avec couleurs ANSI"""
    
    # Codes couleur ANSI
    COLORS = {
        'DEBUG': '\033[36m',     # Cyan
        'INFO': '\033[32m',      # Vert
        'WARNING': '\033[33m',   # Jaune
        'ERROR': '\033[31m',     # Rouge
        'CRITICAL': '\033[35m',  # Magenta
        'RESET': '\033[0m',      # Reset
    }
    
    def format(self, record):
        # Ajouter couleur
        levelname = record.levelname
        if levelname in self.COLORS:
            record.levelname = f"{self.COLORS[levelname]}{levelname}{self.COLORS['RESET']}"
        
        return super().format(record)

# Utilisation
handler = logging.StreamHandler()
handler.setFormatter(ColoredFormatter(
    '%(asctime)s - %(levelname)s - %(message)s'
))

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)


# === Logging avec rich (bibliothèque) ===

from rich.logging import RichHandler
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(message)s",
    handlers=[RichHandler(rich_tracebacks=True)]
)

logger = logging.getLogger("rich")
logger.info("Message avec [bold red]rich[/bold red]!")


[OK] FILTRES (FILTERS)

# === Créer un filtre personnalisé ===

import logging

class LevelFilter(logging.Filter):
    """Filtre qui accepte seulement certains niveaux"""
    
    def __init__(self, levels):
        self.levels = levels
    
    def filter(self, record):
        return record.levelno in self.levels

# Utilisation: Logger seulement WARNING
handler = logging.StreamHandler()
handler.addFilter(LevelFilter([logging.WARNING]))


# === Filtre par nom de module ===

class ModuleFilter(logging.Filter):
    """Filtre par nom de module"""
    
    def __init__(self, module_name):
        self.module_name = module_name
    
    def filter(self, record):
        return record.module == self.module_name


# === Filtre pour ignorer certains messages ===

class IgnoreFilter(logging.Filter):
    """Ignore messages contenant certains mots"""
    
    def __init__(self, keywords):
        self.keywords = keywords
    
    def filter(self, record):
        message = record.getMessage()
        return not any(keyword in message for keyword in self.keywords)

# Ignorer logs contenant "debug" ou "test"
handler.addFilter(IgnoreFilter(['debug', 'test']))


# === Filtre pour throttling (rate limiting) ===

import time

class ThrottleFilter(logging.Filter):
    """Limite taux de logs (max N par seconde)"""
    
    def __init__(self, rate=1.0):
        self.rate = rate
        self.last_log = {}
    
    def filter(self, record):
        now = time.time()
        key = (record.module, record.levelno, record.msg)
        
        if key in self.last_log:
            if now - self.last_log[key] < 1.0 / self.rate:
                return False
        
        self.last_log[key] = now
        return True

# Max 5 logs par seconde
handler.addFilter(ThrottleFilter(rate=5.0))


[OK] ROTATION DE FICHIERS

# === Rotation par taille ===

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    'app.log',
    maxBytes=10*1024*1024,  # 10 MB
    backupCount=5,           # app.log, app.log.1, ..., app.log.5
    encoding='utf-8'
)

# Quand app.log atteint 10MB:
# app.log.4 -> app.log.5 (supprimé si 5 fichiers déjà)
# app.log.3 -> app.log.4
# app.log.2 -> app.log.3
# app.log.1 -> app.log.2
# app.log -> app.log.1
# Nouveau app.log créé


# === Rotation par temps ===

from logging.handlers import TimedRotatingFileHandler

# Rotation quotidienne à minuit
handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',
    interval=1,
    backupCount=30,  # Garde 30 jours
    encoding='utf-8'
)

# Options pour 'when':
# 'S' - Secondes
# 'M' - Minutes
# 'H' - Heures
# 'D' - Jours
# 'midnight' - À minuit
# 'W0'-'W6' - Jour de semaine (0=Lundi, 6=Dimanche)

# Exemples:
# Toutes les heures
handler = TimedRotatingFileHandler('app.log', when='H', interval=1, backupCount=24)

# Tous les lundis
handler = TimedRotatingFileHandler('app.log', when='W0', interval=1, backupCount=4)

# Toutes les 6 heures
handler = TimedRotatingFileHandler('app.log', when='H', interval=6, backupCount=28)

# Chaque minute (debug)
handler = TimedRotatingFileHandler('app.log', when='M', interval=1, backupCount=60)


# === Rotation avec compression ===

import gzip
import shutil
from logging.handlers import RotatingFileHandler

class CompressedRotatingFileHandler(RotatingFileHandler):
    """RotatingFileHandler avec compression gzip"""
    
    def doRollover(self):
        """Override pour ajouter compression"""
        super().doRollover()
        
        # Compresser le fichier roté
        log_file = f"{self.baseFilename}.1"
        if os.path.exists(log_file):
            with open(log_file, 'rb') as f_in:
                with gzip.open(f"{log_file}.gz", 'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
            os.remove(log_file)

# Utilisation
handler = CompressedRotatingFileHandler(
    'app.log',
    maxBytes=10*1024*1024,
    backupCount=5
)


# === Rotation avec horodatage dans nom ===

from logging.handlers import TimedRotatingFileHandler
import time

class TimestampedRotatingFileHandler(TimedRotatingFileHandler):
    """Ajoute timestamp dans nom fichier roté"""
    
    def rotation_filename(self, default_name):
        """Override pour personnaliser nom"""
        # Format: app.log.2024-01-15_14-30-22
        timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
        return f"{self.baseFilename}.{timestamp}"

handler = TimestampedRotatingFileHandler(
    'app.log',
    when='midnight',
    backupCount=30
)


[OK] LOGGING DANS APPLICATIONS WEB

# === Flask ===

from flask import Flask, request
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# Configuration logging
if not app.debug:
    # Handler fichier
    file_handler = RotatingFileHandler(
        'flask_app.log',
        maxBytes=10240000,
        backupCount=10
    )
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)
    app.logger.addHandler(file_handler)
    
    app.logger.setLevel(logging.INFO)
    app.logger.info('Application Flask démarrée')

# Middleware pour logger requêtes
@app.before_request
def log_request():
    app.logger.info(f"{request.method} {request.path}")

@app.route('/')
def index():
    app.logger.debug("Route index appelée")
    return "Hello World"


# === Django ===

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'django_app.log',
            'maxBytes': 1024 * 1024 * 10,  # 10 MB
            'backupCount': 5,
            'formatter': 'verbose',
        },
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file', 'console'],
            'level': 'INFO',
            'propagate': False,
        },
        'myapp': {
            'handlers': ['file', 'console'],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
    'root': {
        'handlers': ['console'],
        'level': 'INFO',
    },
}

# views.py
import logging

logger = logging.getLogger('myapp')

def my_view(request):
    logger.info(f"Vue appelée par {request.user}")
    # Code...


# === FastAPI ===

from fastapi import FastAPI, Request
import logging
import time

app = FastAPI()

# Configuration logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Middleware pour logger requêtes
@app.middleware("http")
async def log_requests(request: Request, call_next):
    start_time = time.time()
    
    logger.info(f"Requête: {request.method} {request.url.path}")
    
    response = await call_next(request)
    
    process_time = time.time() - start_time
    logger.info(f"Réponse: {response.status_code} - {process_time:.3f}s")
    
    return response

@app.get("/")
async def root():
    logger.debug("Route root appelée")
    return {"message": "Hello World"}


# === API REST avec logging structuré ===

from fastapi import FastAPI, Request
import logging
import json
import time
import uuid

class APILogFormatter(logging.Formatter):
    """Formatter pour API REST"""
    
    def format(self, record):
        log_data = {
            'timestamp': self.formatTime(record),
            'level': record.levelname,
            'message': record.getMessage(),
        }
        
        # Ajouter contexte API si présent
        if hasattr(record, 'request_id'):
            log_data['request_id'] = record.request_id
        if hasattr(record, 'user_id'):
            log_data['user_id'] = record.user_id
        if hasattr(record, 'endpoint'):
            log_data['endpoint'] = record.endpoint
        if hasattr(record, 'duration'):
            log_data['duration_ms'] = record.duration
        
        return json.dumps(log_data)

# Configuration
handler = logging.StreamHandler()
handler.setFormatter(APILogFormatter())
logger = logging.getLogger('api')
logger.addHandler(handler)
logger.setLevel(logging.INFO)

app = FastAPI()

@app.middleware("http")
async def log_api_requests(request: Request, call_next):
    request_id = str(uuid.uuid4())
    start_time = time.time()
    
    # Logger début requête
    logger.info(
        "API Request",
        extra={
            'request_id': request_id,
            'endpoint': request.url.path,
            'method': request.method,
        }
    )
    
    response = await call_next(request)
    
    # Logger fin requête
    duration = (time.time() - start_time) * 1000
    logger.info(
        "API Response",
        extra={
            'request_id': request_id,
            'status_code': response.status_code,
            'duration': duration,
        }
    )
    
    return response


[OK] LOGGING MULTI-PROCESSUS ET MULTI-THREAD

# === Logging thread-safe ===

# Le module logging est thread-safe par défaut
import logging
import threading

logger = logging.getLogger(__name__)

def worker(name):
    logger.info(f"Thread {name} démarré")
    # Traitement...
    logger.info(f"Thread {name} terminé")

# Créer threads
threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(f"T{i}",))
    threads.append(t)
    t.start()

for t in threads:
    t.join()


# === Logging multi-processus (approche queue) ===

import logging
import multiprocessing
from logging.handlers import QueueHandler, QueueListener

def worker_process(queue, name):
    """Processus worker"""
    # Configurer logger pour utiliser queue
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logger.addHandler(QueueHandler(queue))
    
    # Logger normalement
    logger.info(f"Processus {name} démarré")
    # Traitement...
    logger.info(f"Processus {name} terminé")

def main():
    # Créer queue pour logs
    log_queue = multiprocessing.Queue()
    
    # Handler pour écrire logs
    file_handler = logging.FileHandler('multiprocess.log')
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s - %(processName)s - %(levelname)s - %(message)s'
    ))
    
    # Listener qui écoute la queue
    listener = QueueListener(log_queue, file_handler)
    listener.start()
    
    # Créer processus
    processes = []
    for i in range(5):
        p = multiprocessing.Process(
            target=worker_process,
            args=(log_queue, f"P{i}")
        )
        processes.append(p)
        p.start()
    
    # Attendre fin
    for p in processes:
        p.join()
    
    # Arrêter listener
    listener.stop()

if __name__ == '__main__':
    main()


# === Logging avec concurrent.futures ===

import logging
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

def task(n):
    logger.info(f"Début tâche {n}")
    time.sleep(1)
    logger.info(f"Fin tâche {n}")
    return n * 2

# Thread pool
with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(task, i) for i in range(5)]
    results = [f.result() for f in futures]


[OK] LOGGING POUR DEBUGGING

# === Logger toutes les variables ===

import logging

logger = logging.getLogger(__name__)

def debug_function():
    x = 10
    y = 20
    result = x + y
    
    # Logger toutes les variables locales
    logger.debug(f"Variables locales: {locals()}")
    
    return result


# === Logging avec traceback détaillé ===

import logging
import traceback
import sys

logger = logging.getLogger(__name__)

def detailed_error_log():
    try:
        # Code qui peut échouer
        result = 10 / 0
    except Exception as e:
        # Méthode 1: exc_info
        logger.error("Erreur détectée", exc_info=True)
        
        # Méthode 2: exception (raccourci)
        logger.exception("Erreur détectée")
        
        # Méthode 3: Traceback complet
        exc_type, exc_value, exc_traceback = sys.exc_info()
        logger.error("Traceback complet:\n%s", 
                    ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)))


# === Logging temporaire pour debug ===

import logging

# Activer debug temporairement
def enable_debug():
    logging.getLogger().setLevel(logging.DEBUG)
    
def disable_debug():
    logging.getLogger().setLevel(logging.INFO)

# Ou avec context manager
from contextlib import contextmanager

@contextmanager
def debug_logging():
    """Active debug temporairement"""
    old_level = logging.getLogger().level
    logging.getLogger().setLevel(logging.DEBUG)
    try:
        yield
    finally:
        logging.getLogger().setLevel(old_level)

# Utilisation
with debug_logging():
    # Code avec debug activé
    logger.debug("Message debug temporaire")
# Debug désactivé après le with


# === Logging avec inspection de stack ===

import logging
import inspect

logger = logging.getLogger(__name__)

def log_call_stack():
    """Loggue la pile d'appels"""
    stack = inspect.stack()
    logger.debug("Pile d'appels:")
    for frame_info in stack[1:]:  # Ignorer cette fonction
        logger.debug(f"  {frame_info.filename}:{frame_info.lineno} - {frame_info.function}")

def function_a():
    function_b()

def function_b():
    log_call_stack()


# === Logging conditionnel ===

import logging

logger = logging.getLogger(__name__)

DEBUG = True  # Flag global

def conditional_log(message, condition=True):
    """Log seulement si condition vraie"""
    if condition:
        logger.debug(message)

# Ou avec lambda pour lazy evaluation
def lazy_log(level, message_func):
    """Évalue message seulement si niveau actif"""
    if logger.isEnabledFor(level):
        logger.log(level, message_func())

# Utilisation
lazy_log(logging.DEBUG, lambda: f"Résultat coûteux: {expensive_operation()}")


[OK] MONITORING ET ALERTES

# === Envoyer email sur erreur ===

from logging.handlers import SMTPHandler
import logging

logger = logging.getLogger(__name__)

# Handler email
smtp_handler = SMTPHandler(
    mailhost=('smtp.gmail.com', 587),
    fromaddr='app@example.com',
    toaddrs=['admin@example.com', 'dev@example.com'],
    subject='Erreur Application Critique',
    credentials=('username', 'password'),
    secure=()
)

# Format détaillé pour emails
email_formatter = logging.Formatter('''
Application: Mon App
Environnement: Production
Serveur: server-01

Time: %(asctime)s
Level: %(levelname)s
Module: %(module)s
Function: %(funcName)s
Line: %(lineno)d

Message:
%(message)s

Traceback:
%(exc_text)s
''')

smtp_handler.setFormatter(email_formatter)
smtp_handler.setLevel(logging.ERROR)  # Seulement erreurs

logger.addHandler(smtp_handler)


# === Logging vers Syslog ===

from logging.handlers import SysLogHandler
import logging

logger = logging.getLogger(__name__)

# Handler syslog
syslog_handler = SysLogHandler(address='/dev/log')  # Linux
# syslog_handler = SysLogHandler(address=('localhost', 514))  # UDP

syslog_handler.setLevel(logging.WARNING)

logger.addHandler(syslog_handler)


# === Logging vers service externe (ex: Sentry) ===

import logging
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration

# Configuration Sentry
sentry_logging = LoggingIntegration(
    level=logging.INFO,        # Capture info et plus
    event_level=logging.ERROR  # Envoie erreurs comme events
)

sentry_sdk.init(
    dsn="your-sentry-dsn",
    integrations=[sentry_logging]
)

# Logging normal
logger = logging.getLogger(__name__)
logger.error("Ceci sera envoyé à Sentry")


# === Logging vers base de données ===

import logging
import sqlite3
from datetime import datetime

class DatabaseHandler(logging.Handler):
    """Handler qui écrit dans base de données"""
    
    def __init__(self, db_path):
        super().__init__()
        self.db_path = db_path
        self._init_db()
    
    def _init_db(self):
        """Crée table si nécessaire"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME,
                level VARCHAR(10),
                logger VARCHAR(100),
                message TEXT,
                module VARCHAR(100),
                function VARCHAR(100),
                line_no INTEGER
            )
        ''')
        conn.commit()
        conn.close()
    
    def emit(self, record):
        """Écrit log dans DB"""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            cursor.execute('''
                INSERT INTO logs (timestamp, level, logger, message, module, function, line_no)
                VALUES (?, ?, ?, ?, ?, ?, ?)
            ''', (
                datetime.fromtimestamp(record.created),
                record.levelname,
                record.name,
                self.format(record),
                record.module,
                record.funcName,
                record.lineno
            ))
            
            conn.commit()
            conn.close()
        except Exception:
            self.handleError(record)

# Utilisation
logger = logging.getLogger(__name__)
db_handler = DatabaseHandler('logs.db')
logger.addHandler(db_handler)


# === Logging vers API HTTP ===

import logging
import requests
import json

class HTTPHandler(logging.Handler):
    """Handler qui POST logs vers API"""
    
    def __init__(self, url, api_key=None):
        super().__init__()
        self.url = url
        self.api_key = api_key
    
    def emit(self, record):
        """Envoie log vers API"""
        try:
            log_data = {
                'timestamp': record.created,
                'level': record.levelname,
                'logger': record.name,
                'message': self.format(record),
                'module': record.module,
                'function': record.funcName,
                'line': record.lineno,
            }
            
            headers = {'Content-Type': 'application/json'}
            if self.api_key:
                headers['Authorization'] = f'Bearer {self.api_key}'
            
            requests.post(
                self.url,
                data=json.dumps(log_data),
                headers=headers,
                timeout=5
            )
        except Exception:
            self.handleError(record)

# Utilisation
logger = logging.getLogger(__name__)
http_handler = HTTPHandler('https://logs.example.com/api/logs', api_key='secret')
http_handler.setLevel(logging.ERROR)
logger.addHandler(http_handler)


[OK] PERFORMANCE ET OPTIMISATION

# === Lazy evaluation ===

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# [X] MAUVAIS: Calcul inutile si DEBUG désactivé
logger.debug(f"Résultat: {expensive_calculation()}")  # Appelé même si pas loggé!

# [OK] BON: % formatting (lazy)
logger.debug("Résultat: %s", expensive_calculation())  # Appelé seulement si loggé

# [OK] BON: Vérifier niveau avant
if logger.isEnabledFor(logging.DEBUG):
    logger.debug(f"Résultat: {expensive_calculation()}")


# === Désactiver logging en production ===

import logging

# Désactiver tous les logs DEBUG en production
logging.disable(logging.DEBUG)

# Réactiver
logging.disable(logging.NOTSET)


# === Logging asynchrone pour performance ===

from logging.handlers import QueueHandler, QueueListener
import logging
import queue

# Setup asynchrone
log_queue = queue.Queue(-1)  # Taille illimitée
queue_handler = QueueHandler(log_queue)

# Handlers réels
file_handler = logging.FileHandler('app.log')
console_handler = logging.StreamHandler()

# Listener (thread séparé)
listener = QueueListener(
    log_queue,
    file_handler,
    console_handler,
    respect_handler_level=True
)
listener.start()

# Logger principal utilise queue
root = logging.getLogger()
root.addHandler(queue_handler)
root.setLevel(logging.DEBUG)

# Logs sont traités de manière asynchrone
logger = logging.getLogger(__name__)
logger.info("Message asynchrone")  # Retourne immédiatement


# === Caching de loggers ===

import logging

# Cache de loggers pour éviter recréation
_logger_cache = {}

def get_logger(name):
    """Récupère logger depuis cache"""
    if name not in _logger_cache:
        _logger_cache[name] = logging.getLogger(name)
    return _logger_cache[name]


# === Profiling du logging ===

import logging
import time

class ProfilingHandler(logging.Handler):
    """Handler qui mesure temps d'écriture"""
    
    def __init__(self, wrapped_handler):
        super().__init__()
        self.wrapped_handler = wrapped_handler
        self.total_time = 0
        self.count = 0
    
    def emit(self, record):
        start = time.time()
        self.wrapped_handler.emit(record)
        duration = time.time() - start
        
        self.total_time += duration
        self.count += 1
    
    def get_stats(self):
        if self.count == 0:
            return {'count': 0, 'total_time': 0, 'avg_time': 0}
        return {
            'count': self.count,
            'total_time': self.total_time,
            'avg_time': self.total_time / self.count
        }


[OK] BONNES PRATIQUES

# === Structure recommandée ===

# [OK] BON: Créer logger au niveau module
import logging

logger = logging.getLogger(__name__)  # Utiliser __name__

def my_function():
    logger.info("Message")


# [X] MAUVAIS: Créer logger dans fonction
def my_function():
    logger = logging.getLogger(__name__)  # Recréé à chaque appel
    logger.info("Message")


# === Niveaux de log appropriés ===

import logging

logger = logging.getLogger(__name__)

# DEBUG: Informations détaillées pour diagnostic
logger.debug(f"Variables: x={x}, y={y}, z={z}")
logger.debug("Connexion DB établie")

# INFO: Confirmation du bon fonctionnement
logger.info("Application démarrée")
logger.info("Utilisateur connecté: {user}")
logger.info("Traitement terminé avec succès")

# WARNING: Inattendu mais pas bloquant
logger.warning("Cache expiré, rechargement")
logger.warning("Tentative de reconnexion (3/5)")
logger.warning("API deprecated utilisée")

# ERROR: Erreur sérieuse, fonction échouée
logger.error("Impossible de se connecter à la DB")
logger.error("Fichier non trouvé: %s", filename)

# CRITICAL: Erreur grave, app peut crash
logger.critical("Out of memory")
logger.critical("Disque plein, impossible d'écrire")


# === Messages clairs et actionnables ===

# [OK] BON: Messages informatifs
logger.error("Connexion DB échouée: timeout après 30s (host=db.example.com, port=5432)")
logger.warning("Espace disque faible: 95% utilisé sur /var/log (24GB/25GB)")

# [X] MAUVAIS: Messages vagues
logger.error("Erreur")
logger.warning("Problème")


# === Ne pas logger d'informations sensibles ===

# [X] MAUVAIS: Données sensibles
logger.info(f"Connexion utilisateur: {username} / {password}")  # NON!
logger.debug(f"Token: {api_token}")  # NON!
logger.info(f"Carte bancaire: {card_number}")  # NON!

# [OK] BON: Données anonymisées
logger.info(f"Connexion utilisateur: {username}")
logger.debug(f"Token présent: {bool(api_token)}")
logger.info(f"Paiement avec carte ****{card_number[-4:]}")


# === Utiliser exceptions correctement ===

# [OK] BON
try:
    process_data()
except ValueError as e:
    logger.exception("Erreur lors du traitement")  # Inclut traceback
    raise  # Re-raise après log

# [X] MAUVAIS
try:
    process_data()
except Exception:
    logger.error("Erreur")  # Pas de traceback, info perdue
    pass  # Erreur cachée!


# === Configuration centralisée ===

# config/logging.py
import logging.config

def setup_logging(config_path='logging.yaml'):
    """Configure logging pour toute l'application"""
    import yaml
    
    with open(config_path) as f:
        config = yaml.safe_load(f)
        logging.config.dictConfig(config)

# main.py
from config.logging import setup_logging

def main():
    setup_logging()
    # Reste du code...


# === Tests avec logging ===

import logging
import unittest
from io import StringIO

class TestMyFunction(unittest.TestCase):
    def setUp(self):
        # Capturer logs pendant tests
        self.log_capture = StringIO()
        self.handler = logging.StreamHandler(self.log_capture)
        self.logger = logging.getLogger('mymodule')
        self.logger.addHandler(self.handler)
        self.logger.setLevel(logging.DEBUG)
    
    def tearDown(self):
        self.logger.removeHandler(self.handler)
    
    def test_logging_output(self):
        # Code qui logue
        self.logger.info("Test message")
        
        # Vérifier log
        log_contents = self.log_capture.getvalue()
        self.assertIn("Test message", log_contents)


[OK] DÉPANNAGE

# === Problème: Logs n'apparaissent pas ===

# Causes possibles:
# 1. Niveau trop élevé
logger.setLevel(logging.DEBUG)  # Vérifier niveau

# 2. Pas de handler
if not logger.handlers:
    logger.addHandler(logging.StreamHandler())

# 3. Propagation désactivée
logger.propagate = True

# 4. basicConfig() appelé trop tard
# Solution: Appeler au début du programme


# === Problème: Logs dupliqués ===

# Cause: Handlers ajoutés plusieurs fois
# Solution: Vérifier avant d'ajouter
if not logger.handlers:
    logger.addHandler(handler)

# Ou nettoyer handlers existants
logger.handlers.clear()
logger.addHandler(handler)


# === Problème: Encoding errors ===

# Solution: Spécifier encoding
handler = logging.FileHandler('app.log', encoding='utf-8')

# Ou pour Python < 3.9
import codecs
handler = logging.FileHandler('app.log')
handler.stream = codecs.open('app.log', 'a', encoding='utf-8')


# === Problème: Performance ===

# Solutions:
# 1. Niveau approprié en production
logging.basicConfig(level=logging.WARNING)  # Pas DEBUG

# 2. Logging asynchrone
# Utiliser QueueHandler

# 3. Désactiver logs inutiles
logging.getLogger('requests').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING)


# === Problème: Rotation ne fonctionne pas ===

# Cause: Fichier ouvert par autre processus
# Solution: Utiliser WatchedFileHandler (Unix)
from logging.handlers import WatchedFileHandler

handler = WatchedFileHandler('app.log')

# Ou utiliser rotation externe (logrotate sur Linux)


[OK] EXEMPLES COMPLETS

# === Application complète ===

# main.py
import logging
import logging.config
import yaml
from pathlib import Path

def setup_logging(
    default_path='logging.yaml',
    default_level=logging.INFO,
    env_key='LOG_CFG'
):
    """Setup logging configuration"""
    import os
    
    path = default_path
    value = os.getenv(env_key, None)
    if value:
        path = value
    
    if Path(path).exists():
        with open(path, 'r') as f:
            config = yaml.safe_load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level=default_level)

def main():
    setup_logging()
    logger = logging.getLogger(__name__)
    
    logger.info("="*50)
    logger.info("Application démarrée")
    logger.info("="*50)
    
    try:
        # Code principal
        process()
        
        logger.info("Application terminée avec succès")
    except Exception as e:
        logger.exception("Erreur fatale")
        raise
    finally:
        logger.info("Nettoyage terminé")

def process():
    logger = logging.getLogger(__name__)
    logger.debug("Début traitement")
    # Code...
    logger.debug("Fin traitement")

if __name__ == '__