zpp_logs 2.2.0
Module de gestion de logs
License: MIT License
Keywords: logs module zephyroff

# zpp_logs: Un Module de Logging Python Flexible et Puissant

`zpp_logs` est un module de logging Python conÃ§u pour offrir une flexibilitÃ© maximale dans la configuration et l'utilisation des journaux d'Ã©vÃ©nements. InspirÃ© par le module `logging` standard de Python, il introduit des fonctionnalitÃ©s avancÃ©es telles que la configuration via YAML, le formatage basÃ© sur Jinja2 avec des rÃ¨gles dynamiques, des filtres personnalisables et une gestion avancÃ©e des handlers.

## FonctionnalitÃ©s ClÃ©s

*   **Configuration Flexible** : Configurez l'intÃ©gralitÃ© de votre systÃ¨me de logging via un fichier YAML ou directement en Python.
*   **Niveaux de Log PersonnalisÃ©s** : Inclut un niveau `SUCCESS` (25) en plus des niveaux standards.
*   **Formatage AvancÃ© avec Jinja2** :
    *   Utilisez des templates Jinja2 pour dÃ©finir le format de vos messages de log.
    *   Fonctions Jinja2 personnalisÃ©es (`fg`, `attr`, `date`, `epoch`) pour un formatage riche (ex: couleurs dans la console).
    *   **RÃ¨gles de Formatage Dynamiques** : Appliquez des transformations conditionnelles aux champs de vos logs basÃ©es sur des expressions Jinja2, avec un comportement par dÃ©faut (`__default__`).
*   **Filtrage Puissant** : Filtrez les messages de log au niveau du handler en utilisant des expressions Jinja2.
*   **Handlers Multiples** :
    *   `ConsoleHandler` : Ã‰crit les logs dans la console (stdout/stderr).
    *   `FileHandler` : Ã‰crit les logs dans un fichier, avec rotation basÃ©e sur la taille (`maxBytes`, `backupCount`) et support du logging circulaire.
    *   `DatabaseHandler` : Enregistre les logs dans une base de donnÃ©es (SQLite, MySQL, etc.) avec mappage de colonnes personnalisable via Jinja2 et colonnes par dÃ©faut.
    *   `SMTPHandler` : Envoie les logs par e-mail via SMTP.
    *   `ResendHandler` : Envoie les logs par e-mail via l'API Resend.
*   **Modification Dynamique** : Modifiez les propriÃ©tÃ©s des formatters, handlers et loggers Ã  la volÃ©e aprÃ¨s leur crÃ©ation.

## Installation

1.  **Cloner le dÃ©pÃ´t** (ou crÃ©er la structure de fichiers) :
    ```bash
    mkdir zpp_logs
    # CrÃ©ez les fichiers .py Ã  l'intÃ©rieur de zpp_logs/
    # CrÃ©ez config.yaml et main.py Ã  la racine
    ```
2.  **Installer les dÃ©pendances** :
    ```bash
    pip install -r requirements.txt
    ```
    Contenu de `requirements.txt` :
    ```
    pyyaml
    jinja2
    colorama
    requests
    SQLAlchemy
    ```

## Concepts ClÃ©s

### Niveaux de Log

Les niveaux de log sont des entiers, avec des constantes prÃ©dÃ©finies :
`CRITICAL` (50), `ERROR` (40), `WARNING` (30), `SUCCESS` (25), `INFO` (20), `DEBUG` (10), `NOTSET` (0).

### Loggers

Les loggers sont les points d'entrÃ©e pour enregistrer les messages. Ils possÃ¨dent un nom et une liste de handlers.

#### Approche dynamique

##### 1. Instanciation d'un Logger
Un Logger a besoin d'un nom (name) et d'une liste de handlers pour Ãªtre crÃ©Ã©.

```python
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.console import ConsoleHandler
from zpp_logs.levels import DEBUG

# Ã‰tape 1: CrÃ©er un formateur
my_formatter = CustomFormatter("{{ levelname }}: {{ msg }}")

# Ã‰tape 2: CrÃ©er un ou plusieurs handlers
console_handler = ConsoleHandler(level=DEBUG, formatter=my_formatter)

# Ã‰tape 3: CrÃ©er le logger
logger = Logger(
    name="my_app",
    handlers=[console_handler]
)
```

##### 2. Ã‰mission de Logs
Une fois le logger crÃ©Ã©, utilisez ses mÃ©thodes de niveau pour Ã©mettre des messages.

```python
logger.debug("Information de dÃ©bogage dÃ©taillÃ©e.")
logger.info("DÃ©marrage du processus.")
logger.warning("Le disque est presque plein.")
logger.error("Impossible de contacter le service externe.")
logger.critical("Ã‰chec critique, arrÃªt de l'application.")
```


##### 3. Passer des DonnÃ©es SupplÃ©mentaires
Une fonctionnalitÃ© clÃ© est la capacitÃ© de passer des donnÃ©es structurÃ©es en utilisant des arguments clÃ©-valeur (**kwargs). Ces donnÃ©es sont ajoutÃ©es au record du log et deviennent disponibles dans votre CustomFormatter (Ã  la fois pour le formatage et pour les rÃ¨gles).

```python
# Le formateur peut utiliser 'user_id' et 'ip_address'
formatter = CustomFormatter("{{ msg }} (user: {{ user_id }}, ip: {{ ip_address }})")
logger.add_handler(ConsoleHandler(level=DEBUG, formatter=formatter))


# Passez les donnÃ©es en kwargs lors de l'appel de log
logger.info(
    "Tentative de connexion de l'utilisateur.",
    user_id="alice",
    ip_address="192.168.1.100"
)
# Sortie: Tentative de connexion de l'utilisateur. (user: alice, ip: 192.168.1.100)
```

#### Approche par instance de config

```python
from zpp_logs.core import LogManager

# Une seule instance de manager pour toute l'application
manager = LogManager('config.yaml')

# Obtenir le logger nommÃ© 'database'
db_logger = manager.get_logger('database')
db_logger.debug("Connexion Ã  la base de donnÃ©es...") # Ira dans app.log

# Obtenir le logger 'root'
root_logger = manager.get_logger('root')
root_logger.info("Message d'information gÃ©nÃ©ral.") # Ira dans la console

# Demander un logger qui n'est pas dans la config
# Le LogManager renverra la configuration du logger 'root' !
api_logger = manager.get_logger('external_api')
api_logger.warning("ProblÃ¨me avec l'API externe.") # Ira dans la console
```

### Formatters

DÃ©finissent l'apparence des messages de log.
*   **`format_str`** : Une chaÃ®ne de template Jinja2 (ex: `"{{ date('%H:%M:%S') }} | {{ levelname }} | {{ msg }}"`).
*   **RÃ¨gles (`rules`)** : Un dictionnaire oÃ¹ les clÃ©s sont des expressions Jinja2 (Ã©valuÃ©es Ã  `True` ou `False`) et les valeurs sont des dictionnaires de champs Ã  modifier. La clÃ© `__default__` agit comme une clause `else`.

    ```yaml
    # Exemple de rÃ¨gles dans un formatter
    rules:
        "levelname == 'SUCCESS'":
          levelname: "{{ fg('green') }}SUCCESS{{ attr(0) }}"
          msg: "{{ fg('green') }}OpÃ©ration rÃ©ussie : {{ msg }}{{ attr(0) }}"
        "levelname == 'ERROR' and 'database' in msg":
          levelname: "{{ fg('yellow') }}DB_ERROR{{ attr(0) }}"
          msg: "{{ fg('yellow') }}ProblÃ¨me de base de donnÃ©es: {{ msg }}{{ attr(0) }}"
        __default__:
          levelname: "{{ fg('gray') }}DEFAULT{{ attr(0) }}"
          msg: "{{ fg('gray') }}Message par dÃ©faut: {{ msg }}{{ attr(0) }}"
    ```

### Handlers

Les handlers sont responsables de l'envoi des messages de log vers des destinations spÃ©cifiques (console, fichier, base de donnÃ©es, e-mail, etc.). Chaque handler peut Ãªtre configurÃ© indÃ©pendamment.

*   **`level`** : Le niveau minimum du message pour que le handler le traite. Un message avec un niveau infÃ©rieur Ã  celui du handler sera ignorÃ©. Peut Ãªtre une constante de `zpp_logs` (ex: `zpp_logs.INFO`) ou une chaÃ®ne de caractÃ¨re (ex: `INFO`).
*   **`ops`** : L'opÃ©rateur de comparaison du niveau. DÃ©finit comment le niveau du message est comparÃ© au `level` du handler.
    *   `">="` (par dÃ©faut) : Le handler traite les messages dont le niveau est supÃ©rieur ou Ã©gal Ã  son `level`.
    *   `">"` : Strictement supÃ©rieur.
    *   `"<="` : InfÃ©rieur ou Ã©gal.
    *   `"<"` : Strictement infÃ©rieur.
    *   `"=="` : Ã‰gal.
    *   `"!="` : DiffÃ©rent.
*   **`formatter`** : Le nom de l'instance du formatter Ã  utiliser pour ce handler, tel que dÃ©fini dans la section `formatters` du `config.yaml`.
*   **`filters`** : Une liste d'expressions Jinja2. Si une expression Ã©value Ã  `False` pour un message donnÃ©, ce message est filtrÃ© et n'est pas traitÃ© par le handler. Utile pour des filtrages complexes basÃ©s sur le contenu du message ou d'autres attributs du record de log.

### ConsoleHandler

Le `ConsoleHandler` est conÃ§u pour afficher les messages de log directement dans la console (sortie standard ou erreur standard).

#### FonctionnalitÃ©

Ce handler est l'un des plus simples. Il prend un message de log, le formate en utilisant le `CustomFormatter` fourni, et l'Ã©crit sur le flux de sortie spÃ©cifiÃ©, qui est par dÃ©faut `sys.stdout` (la sortie standard). Il est idÃ©al pour le dÃ©veloppement ou pour les applications en ligne de commande oÃ¹ une visibilitÃ© immÃ©diate des logs est nÃ©cessaire.

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum requis pour que le message soit traitÃ© par ce handler. Doit Ãªtre une constante de `zpp_logs.levels` (ex: `INFO`, `DEBUG`). |
| `formatter`| `CustomFormatter` | `None` | Une instance de `CustomFormatter` chargÃ©e de mettre en forme le message avant son affichage. |
| `filters` | `list` | `None` | Une liste de chaÃ®nes de caractÃ¨res. Chaque chaÃ®ne est une expression Jinja2 qui doit retourner `True` pour que le log soit traitÃ©. |
| `output` | `str` | `'sys.stdout'` | Le flux de sortie. Peut Ãªtre `'sys.stdout'` ou `'sys.stderr'`. |
| `ops` | `str` | `'>='` | L'opÃ©rateur de comparaison pour le niveau (`>=`, `==`, etc.). |
| `async_mode` | `bool` | `False` | Si `True`, les logs sont traitÃ©s dans un thread sÃ©parÃ© pour ne pas bloquer l'application principale. |

#### Usage

##### 1. Programmatic (dans un script Python)

Voici comment instancier et utiliser le `ConsoleHandler` directement dans votre code.

```python
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.console import ConsoleHandler
from zpp_logs.levels import INFO

# 1. CrÃ©er un formateur
formatter = CustomFormatter(
    format_str="{{ timestamp.strftime('%H:%M:%S') }} - {{ levelname }} - {{ msg }}"
)

# 2. CrÃ©er une instance du handler
console_handler = ConsoleHandler(level=INFO, formatter=formatter)

# 3. CrÃ©er un logger avec ce handler
logger = Logger(name="my_app", handlers=[console_handler])

# 4. Envoyer un message
logger.info("Ceci est un test pour la console.")
logger.debug("Ce message ne sera pas affichÃ© car son niveau est infÃ©rieur Ã  INFO.")
```

##### 2. DÃ©claratif (dans `config.yaml`)

Voici comment configurer le `ConsoleHandler` dans votre fichier `config.yaml`.

```yaml
# config.yaml

formatters:
  console_format:
    format: "{{ levelname }}: {{ msg }}"

handlers:
  # Nom de notre instance de handler
  console_out:
    # Classe Ã  utiliser
    class: zpp_logs.ConsoleHandler
    # Options du handler
    level: INFO
    formatter: console_format
    output: sys.stdout

loggers:
  root:
    # Associer le handler au logger
    handlers: [console_out]
```


### DatabaseHandler

Le `DatabaseHandler` enregistre les messages de log dans une table de base de donnÃ©es relationnelle.

#### FonctionnalitÃ©

Ce handler puissant utilise `SQLAlchemy` pour se connecter Ã  diffÃ©rentes bases de donnÃ©es (SQLite et MySQL sont supportÃ©s nativement) et y insÃ©rer les enregistrements de log. Il offre une grande flexibilitÃ© pour structurer les donnÃ©es de log.

Il peut fonctionner de deux maniÃ¨res :
1.  **Mode Automatique :** Si vous ne fournissez pas de modÃ¨le SQLAlchemy, le handler crÃ©era automatiquement une table avec des colonnes par dÃ©faut (`id`, `timestamp`, `level`, `logger_name`, `message`) ou selon un mapping que vous spÃ©cifiez.
2.  **Mode ModÃ¨le :** Vous pouvez fournir votre propre classe de modÃ¨le SQLAlchemy. Le handler utilisera la table dÃ©finie par ce modÃ¨le pour y insÃ©rer les logs.

**DÃ©pendances :** Ce handler requiert `SQLAlchemy`. Pour MySQL, vous aurez Ã©galement besoin de `PyMySQL` (`pip install sqlalchemy pymysql`).

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum requis. |
| `formatter`| `CustomFormatter`| `None` | Formateur (moins crucial ici car les donnÃ©es sont mappÃ©es, mais toujours utilisÃ© pour les rÃ¨gles). |
| `connector` | `dict` | `None` | **Requis.** Dictionnaire de connexion Ã  la base de donnÃ©es. |
| `columns` | `dict` | `None` | Optionnel. Mappe les noms de colonnes de la table aux expressions Jinja2 Ã  extraire du log record. |
| `model` | `str` or `DeclarativeMeta`| `None`| Optionnel. Le modÃ¨le SQLAlchemy Ã  utiliser (soit la classe, soit le chemin d'importation).|
| `ops` | `str` | `'>='` | OpÃ©rateur de comparaison pour le niveau. |
| `async_mode` | `bool` | `False` | Si `True`, l'insertion en base de donnÃ©es se fait dans un thread sÃ©parÃ©. |

##### DÃ©tails du `connector`

Le dictionnaire `connector` doit contenir :
- `engine`: `'sqlite'` ou `'mysql'`.
- Pour SQLite : `filename` (chemin du fichier) et `table` (nom de la table).
- Pour MySQL : `host`, `user`, `password`, `database`.

##### DÃ©tails du `columns`

Ce dictionnaire est la clÃ© de la flexibilitÃ©. La clÃ© est le nom de la colonne dans la base de donnÃ©es, la valeur est une expression Jinja2.

Exemple : `{'user_id': 'user.id', 'request_path': 'request.path'}`

#### Usage

##### 1. Programmatic (Mode Automatique)

```python
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.database import DatabaseHandler
from zpp_logs.levels import INFO

# 1. DÃ©finir le connecteur pour une base SQLite
db_connector = {
    'engine': 'sqlite',
    'filename': 'logs/app_logs.db',
    'table': 'activity_logs'
}

# 2. DÃ©finir le mapping des colonnes
# On veut enregistrer le timestamp, le niveau, le message et un 'user_id' personnalisÃ©
column_mapping = {
    'timestamp': 'timestamp',
    'level': 'levelname',
    'message': 'msg',
    'user_id': 'user_id' # 'user_id' sera passÃ© en kwarg
}

# 3. CrÃ©er une instance du handler
db_handler = DatabaseHandler(
    level=INFO,
    formatter=CustomFormatter(""), # Le formateur est moins important ici
    connector=db_connector,
    columns=column_mapping
)

# 4. CrÃ©er un logger
logger = Logger(name="db_app", handlers=[db_handler])

# 5. Envoyer un log avec un champ personnalisÃ©
logger.info("L'utilisateur a changÃ© son mot de passe.", user_id=123)
logger.warning("Tentative de connexion Ã©chouÃ©e.", user_id=456)
```

##### 2. DÃ©claratif (dans `config.yaml`)

```yaml
# config.yaml

formatters:
  # Un formateur vide est suffisant, car le mapping se fait dans le handler
  db_format:
    format: ""

handlers:
  log_to_db:
    class: zpp_logs.DatabaseHandler
    level: INFO
    formatter: db_format
    # Configuration du connecteur
    connector:
      engine: sqlite
      filename: "config_logs.db"
      table: "system_events"
    # Configuration du mapping
    columns:
      timestamp: timestamp
      level: levelname
      message: msg
      logger: name

loggers:
  root:
    handlers: [log_to_db]
```

### FileHandler

Le `FileHandler` Ã©crit les messages de log dans un fichier sur le systÃ¨me de fichiers.

#### FonctionnalitÃ©

Ce handler est utilisÃ© pour la persistance des logs. Il Ã©crit les messages formatÃ©s dans un fichier spÃ©cifiÃ©. Il supporte Ã©galement des fonctionnalitÃ©s avancÃ©es comme la rotation de fichiers (log rotation) basÃ©e sur la taille, ce qui permet de gÃ©rer l'espace disque utilisÃ© par les logs.

- **Rotation Standard :** Quand la taille maximale est atteinte, le fichier de log actuel est renommÃ© (ex: `app.log` -> `app.log.1`) et un nouveau fichier vide est crÃ©Ã©.
- **Rotation Circulaire :** Un mode spÃ©cial oÃ¹, au lieu de crÃ©er de nouveaux fichiers, la ligne la plus ancienne du fichier est supprimÃ©e pour faire de la place.

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum requis. |
| `formatter`| `CustomFormatter`| `None` | L'instance `CustomFormatter` pour la mise en forme. |
| `filters` | `list` | `None` | Filtres Jinja2 pour un contrÃ´le fin. |
| `filename` | `str` | `None` | **Requis.** Le chemin vers le fichier de log. Peut inclure des variables Jinja2. |
| `maxBytes` | `int` | `0` | La taille maximale en octets que le fichier de log peut atteindre avant la rotation. Si `0`, la rotation est dÃ©sactivÃ©e. |
| `backupCount`| `int` | `0` | Le nombre de fichiers de sauvegarde Ã  conserver. Si `> 0`, la rotation standard est utilisÃ©e. Si `0` et `maxBytes > 0`, la rotation circulaire est utilisÃ©e. |
| `ops` | `str` | `'>='` | L'opÃ©rateur de comparaison pour le niveau. |
| `async_mode` | `bool` | `False` | Si `True`, l'Ã©criture des logs se fait dans un thread sÃ©parÃ©. |

#### Usage

##### 1. Programmatic (dans un script Python)

Voici comment configurer un `FileHandler` avec rotation.

```python
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.file import FileHandler
from zpp_logs.levels import DEBUG

# 1. CrÃ©er un formateur dÃ©taillÃ©
formatter = CustomFormatter(
    format_str="{{ timestamp.isoformat() }} | {{ levelname }} | {{ msg }}"
)

# 2. CrÃ©er une instance du handler avec rotation
# Rotation aprÃ¨s 1 MB, conserve 3 fichiers de backup (app.log.1, app.log.2, app.log.3)
file_handler = FileHandler(
    level=DEBUG,
    formatter=formatter,
    filename='app.log',
    maxBytes=1024 * 1024,  # 1 MB
    backupCount=3
)

# 3. CrÃ©er un logger avec ce handler
logger = Logger(name="file_app", handlers=[file_handler])

# 4. Envoyer des messages
logger.info("L'application a dÃ©marrÃ©.")
logger.debug("Ceci est une information de diagnostic.")
```

##### 2. DÃ©claratif (dans `config.yaml`)

Voici comment configurer le `FileHandler` dans votre fichier `config.yaml`.

```yaml
# config.yaml

formatters:
  file_format:
    format: "{{ timestamp }} | {{ name }} | {{ levelname }} | {{ msg }}"

handlers:
  # Nom de notre instance de handler
  log_to_file:
    # Classe Ã  utiliser
    class: zpp_logs.FileHandler
    # Options du handler
    level: DEBUG
    formatter: file_format
    filename: config_app.log
    maxBytes: 512000  # 500 KB
    backupCount: 5
    encoding: utf-8

loggers:
  root:
    handlers: [log_to_file]
```


### ResendHandler

Le `ResendHandler` envoie des emails de log en utilisant l'API du service [Resend](https://resend.com).

#### FonctionnalitÃ©

Ce handler est une alternative moderne au `SMTPHandler`. Il s'intÃ¨gre avec Resend, une plateforme d'envoi d'emails transactionnels pour les dÃ©veloppeurs. Il est idÃ©al pour envoyer des alertes critiques de maniÃ¨re fiable sans avoir Ã  gÃ©rer son propre serveur SMTP.

Le handler envoie une requÃªte POST Ã  l'API de Resend. Le sujet de l'email peut Ãªtre un template Jinja2, et le corps de l'email (`html`) est le message brut (`msg`) du log.

**DÃ©pendances :** Ce handler requiert la librairie `requests` (`pip install requests`).

#### Options


| ParamÃ¨tre     | Type              | DÃ©faut   | Description                                                                                                    |
| ------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `level`       | `int`             | `NOTSET` | Le niveau de log minimum (ex: `ERROR`).                                                                        |
| `formatter`   | `CustomFormatter` | `None`   | Formateur (le corps de l'email est `msg` brut, mais le formateur peut Ãªtre utile pour les rÃ¨gles et le sujet). |
| `host`        | `str`             | `None`   | **Requis.** L'adresse du serveur SMTP.                                                                         |
| `port`        | `int`             | `None`   | **Requis.** Le port du serveur SMTP (ex: 587 pour TLS).                                                        |
| `username`    | `str`             | `None`   | **Requis.** Le nom d'utilisateur pour s'authentifier auprÃ¨s du serveur SMTP.                                   |
| `password`    | `str`             | `None`   | **Requis.** Le mot de passe pour l'authentification.                                                           |
| `fromaddr`    | `str`             | `None`   | **Requis.** L'adresse email de l'expÃ©diteur.                                                                   |
| `toaddrs`     | `list`            | `None`   | **Requis.** Une liste d'adresses email de destinataires.                                                       |
| `subject`     | `str`             | `None`   | **Requis.** Le sujet de l'email. Peut contenir des expressions Jinja2.                                         |
| `ops`         | `str`             | `'>='`   | L'opÃ©rateur de comparaison pour le niveau.                                                                     |
| `async_mode`  | `bool`            | `False`  | Si `True`, l'envoi de l'email se fait dans un thread sÃ©parÃ© pour ne pas bloquer l'application.                 |
| `insecure`    | `bool`            | `False`  | Si `True`, connexion au serveur sans TLS                                                                       |
| `cc`          | `list`            | `None`   | Personne en copie des mails                                                                                    |
| `bcc`         | `list`            | `None`   | Personne en copie des mails                                                                                    |
| `attachments` | `list`            | `None`   | Listes des fichiers Ã  joindre au mail                                                                          |

#### Usage

##### 1. Programmatic (dans un script Python)

**Note :** Ne jamais coder en dur votre clÃ© d'API. Utilisez des variables d'environnement.

```python
import os
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.resend import ResendHandler
from zpp_logs.levels import ERROR

# 1. CrÃ©er une instance du handler
# La clÃ© d'API est rÃ©cupÃ©rÃ©e depuis les variables d'environnement
resend_handler = ResendHandler(
    level=ERROR,
    formatter=CustomFormatter(""), # Corps de l'email = msg
    api_key=os.environ.get('RESEND_API_KEY'),
    fromaddr="onboarding@resend.dev", # Adresse d'exemple fournie par Resend
    to=["votre_email@exemple.com"],
    subject="[ERREUR] Un problÃ¨me est survenu sur {{ name }}"
)

# 2. CrÃ©er un logger avec ce handler
logger = Logger(name="user_service", handlers=[resend_handler])

# 3. Envoyer un log qui dÃ©clenchera l'email via Resend
logger.error("Impossible de mettre Ã  jour le profil de l'utilisateur #500. Erreur de base de donnÃ©es.")
```

##### 2. DÃ©claratif (dans `config.yaml`)

**Attention :** Stocker des clÃ©s d'API en clair dans des fichiers de configuration est une mauvaise pratique.

```yaml
# config.yaml

formatters:
  resend_format:
    format: ""

handlers:
  send_resend_alert:
    class: zpp_logs.ResendHandler
    level: ERROR
    formatter: resend_format
    # --- ParamÃ¨tres Resend ---
    # IdÃ©alement, utilisez un placeholder qui est remplacÃ© au dÃ©marrage de l'app
    api_key: "RE_VOTRE_CLÃ‰_API_Ici"
    fromaddr: "app@votre-domaine-verifie.com"
    to:
      - "devops@exemple.com"
    subject: "Erreur dÃ©tectÃ©e dans le service {{ name }}"

loggers:
  user_service: # Logger spÃ©cifique
    handlers: [send_resend_alert]
  root: # Logger racine
    handlers: [] # Ne pas envoyer d'email pour les logs gÃ©nÃ©raux
```

### SMTPHandler

Le `SMTPHandler` envoie les messages de log par email via un serveur SMTP.

#### FonctionnalitÃ©

Ce handler est particuliÃ¨rement utile pour les notifications d'Ã©vÃ©nements critiques. Lorsqu'un log atteint un certain niveau de sÃ©vÃ©ritÃ© (typiquement `ERROR` ou `CRITICAL`), ce handler peut envoyer un email Ã  une liste de destinataires pour une alerte immÃ©diate.

Le sujet de l'email peut Ãªtre une chaÃ®ne de caractÃ¨res formatÃ©e avec Jinja2, permettant de crÃ©er des sujets dynamiques. Le corps de l'email est le message brut du log (`msg`). La connexion au serveur SMTP se fait via TLS pour plus de sÃ©curitÃ©.

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum (ex: `ERROR`). |
| `formatter`| `CustomFormatter`| `None` | Formateur (le corps de l'email est `msg` brut, mais le formateur peut Ãªtre utile pour les rÃ¨gles et le sujet). |
| `host` | `str` | `None` | **Requis.** L'adresse du serveur SMTP. |
| `port` | `int` | `None` | **Requis.** Le port du serveur SMTP (ex: 587 pour TLS). |
| `username` | `str` | `None` | **Requis.** Le nom d'utilisateur pour s'authentifier auprÃ¨s du serveur SMTP. |
| `password` | `str` | `None` | **Requis.** Le mot de passe pour l'authentification. |
| `fromaddr` | `str` | `None` | **Requis.** L'adresse email de l'expÃ©diteur. |
| `toaddrs` | `list` | `None` | **Requis.** Une liste d'adresses email de destinataires. |
| `subject` | `str` | `None` | **Requis.** Le sujet de l'email. Peut contenir des expressions Jinja2. |
| `ops` | `str` | `'>='` | L'opÃ©rateur de comparaison pour le niveau. |
| `async_mode` | `bool` | `False` | Si `True`, l'envoi de l'email se fait dans un thread sÃ©parÃ© pour ne pas bloquer l'application. |

#### Usage

##### 1. Programmatic (dans un script Python)

**Note :** Pour des raisons de sÃ©curitÃ©, Ã©vitez de coder en dur les identifiants. Utilisez des variables d'environnement ou un gestionnaire de secrets.

```python
import os
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.smtp import SMTPHandler
from zpp_logs.levels import CRITICAL

# 1. CrÃ©er une instance du handler avec les informations du serveur SMTP
# (Ici, nous supposons que les identifiants sont dans des variables d'environnement)
smtp_handler = SMTPHandler(
    level=CRITICAL,
    formatter=CustomFormatter(""), # Le corps est le message brut
    host="smtp.exemple.com",
    port=587,
    username=os.environ.get('SMTP_USER'),
    password=os.environ.get('SMTP_PASS'),
    fromaddr="noreply@monapp.com",
    toaddrs=["admin@exemple.com", "dev-on-call@exemple.com"],
    subject="[ALERTE CRITIQUE] Erreur dans {{ name }}"
)

# 2. CrÃ©er un logger avec ce handler
logger = Logger(name="payment_gateway", handlers=[smtp_handler])

# 3. Envoyer un log critique qui dÃ©clenchera l'envoi d'un email
logger.critical("Ã‰chec du traitement du paiement pour la transaction #12345.")
```

##### 2. DÃ©claratif (dans `config.yaml`)

**Attention :** Stocker des mots de passe en clair dans des fichiers de configuration est une mauvaise pratique de sÃ©curitÃ©. PrÃ©fÃ©rez des mÃ©canismes d'injection de secrets. Cet exemple est Ã  titre illustratif.

```yaml
# config.yaml

formatters:
  email_format:
    format: "" # Non utilisÃ© pour le corps de l'email

handlers:
  send_alert_email:
    class: zpp_logs.SMTPHandler
    level: CRITICAL
    formatter: email_format
    # --- ParamÃ¨tres SMTP ---
    host: smtp.exemple.com
    port: 587
    # IdÃ©alement, utilisez des 'placeholders' que vous remplacez au dÃ©marrage
    username: "MON_USER_SMTP"
    password: "MON_MOT_DE_PASSE_SMTP"
    fromaddr: "alert@system.com"
    toaddrs:
      - "admin-equipe-a@exemple.com"
      - "admin-equipe-b@exemple.com"
    subject: "ALERTE: {{ levelname }} dans le logger '{{ name }}'"

loggers:
  root:
    handlers: [send_alert_email] # Attacher le handler au logger
```


### WebhookHandler

Le `WebhookHandler` envoie les messages de log Ã  une URL de webhook via une requÃªte HTTP POST.

#### FonctionnalitÃ©

Ce handler est extrÃªmement versatile et permet de s'intÃ©grer avec une multitude de services tiers (Slack, Discord, IFTTT, Zapier, etc.) ou avec vos propres endpoints d'API. Il envoie une charge utile (payload) JSON personnalisable Ã  une URL spÃ©cifiÃ©e.

Il supporte plusieurs mÃ©thodes d'authentification pour sÃ©curiser les appels :
- **Bearer Token :** Authentification via un jeton dans l'en-tÃªte `Authorization`.
- **Basic Auth :** Authentification HTTP Basic avec un nom d'utilisateur et un mot de passe.
- **Custom Token :** Authentification via un jeton secret passÃ© dans l'en-tÃªte `X-Webhook-Token`.
- **Aucune :** Si aucune mÃ©thode n'est spÃ©cifiÃ©e, la requÃªte est envoyÃ©e sans authentification.

**DÃ©pendances :** Ce handler requiert la librairie `requests` (`pip install requests`).

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum requis pour dÃ©clencher l'envoi. |
| `formatter`| `CustomFormatter`| `None` | Formateur (utile pour les rÃ¨gles, moins pour le formatage direct). |
| `url` | `str` | `None` | **Requis.** L'URL du webhook Ã  appeler. |
| `data` | `dict` | `{}` | **Requis.** Un dictionnaire dÃ©finissant la structure du JSON Ã  envoyer. Les valeurs sont des templates Jinja2. |
| `bearer` | `str` | `None` | Le jeton (token) Ã  utiliser pour l'authentification de type "Bearer". |
| `basic` | `dict` | `None` | Un dictionnaire avec les clÃ©s `user` and `pass` pour l'authentification "Basic". |
| `token` | `str` | `None` | Le jeton Ã  passer dans l'en-tÃªte `X-Webhook-Token` pour une authentification personnalisÃ©e. |
| `ssl_verify`|`bool` | `True` | Si `False`, la vÃ©rification du certificat SSL de l'URL du webhook sera dÃ©sactivÃ©e. Ã€ utiliser avec prudence. |
| `ops` | `str` | `'>='` | L'opÃ©rateur de comparaison pour le niveau. |
| `async_mode` | `bool` | `False` | Si `True`, l'appel au webhook se fait dans un thread sÃ©parÃ©. |

#### Usage

##### 1. Programmatic (dans un script Python)

```python
import os
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.webhook import WebhookHandler
from zpp_logs.levels import WARNING

# 1. DÃ©finir la structure de la charge utile (payload)
json_payload_template = {
    "content": "Alerte de niveau {{ levelname }} sur le service {{ name }}",
    "embeds": [{
        "title": "DÃ©tails du Log",
        "description": "{{ msg }}",
        "color": 16711680 # Rouge pour les erreurs (exemple pour Discord)
    }],
    "extra_data": {
        "request_id": "{{ request_id | default('N/A') }}"
    }
}

# 2. CrÃ©er une instance du handler
webhook_handler = WebhookHandler(
    level=WARNING,
    formatter=CustomFormatter(""), # Pas besoin de formateur de chaÃ®ne ici
    url=os.environ.get("DISCORD_WEBHOOK_URL"),
    data=json_payload_template
    # Aucune authentification nÃ©cessaire pour les webhooks Discord
)

# 3. CrÃ©er un logger avec ce handler
logger = Logger(name="api_gateway", handlers=[webhook_handler])

# 4. Envoyer un log
logger.warning(
    "Le temps de rÃ©ponse de l'API externe a dÃ©passÃ© le seuil.",
    request_id="a7b2c9x4"
)
```

##### 2. DÃ©claratif (dans `config.yaml`)

C'est ici que ce handler brille par sa lisibilitÃ©.

######## Exemple 1 : Authentification Bearer

```yaml
formatters:
  standard: # Le formateur peut Ãªtre vide si non nÃ©cessaire
    format: ""

handlers:
  send_to_my_api:
    class: zpp_logs.WebhookHandler
    level: INFO
    formatter: standard
    url: "https://api.mon-service.com/v1/log"
    # --- Authentification ---
    bearer: "VOTRE_JETON_API_SECRET"
    # --- DonnÃ©es JSON ---
    data:
      application: "backend-worker"
      source: "production"
      status: "{{ levelname }}"
      message: "{{ msg }}"
      user_context: "{{ user_id | default('system') }}"

loggers:
  root:
    handlers: [send_to_my_api]
```

######## Exemple 2 : Authentification Basic

```yaml
handlers:
  send_to_legacy_system:
    class: zpp_logs.WebhookHandler
    level: ERROR
    formatter: standard
    url: "https://legacy.interne/api/log"
    # --- Authentification ---
    basic:
      user: "api_user"
      pass: "S3cr3tP4ssw0rd"
    # --- DonnÃ©es JSON ---
    data:
      severity: "{{ levelno }}"
      text: "{{ msg }}"

loggers:
  legacy_connector:
    handlers: [send_to_legacy_system]
```

######## Exemple 3 : Authentification par Jeton PersonnalisÃ© (`X-Webhook-Token`)

```yaml
handlers:
  send_to_private_webhook:
    class: zpp_logs.WebhookHandler
    level: INFO
    formatter: standard
    url: "https://mon-service.privÃ©/webhook"
    # --- Authentification ---
    token: "VOTRE_TOKEN_SECRET_PARTAGÃ‰"
    # --- DonnÃ©es JSON ---
    data:
      source: "{{ name }}"
      log_level: "{{ levelname }}"
      log_message: "{{ msg }}"

loggers:
  private_app:
    handlers: [send_to_private_webhook]
```


### DiscordHandler

Le `DiscordHandler` envoie les messages de log sur Discord via un bot, dans un channel ou en message privÃ©. Il supporte les messages simples et les embeds stylisÃ©s avec templates Jinja2.

#### FonctionnalitÃ©

Ce handler permet d'intÃ©grer vos logs directement dans Discord. Il offre deux modes de messagerie :
1. **Messages simples** : Texte brut formatÃ© avec Jinja2.
2. **Embeds** : Messages stylisÃ©s avec titre, description, couleurs, champs personnalisÃ©s, footer, author, images et timestamps.

Les couleurs des embeds supportent les formats hexadÃ©cimaux (`#RRGGBB` ou `RRGGBB`) en plus du format dÃ©cimal Discord.

**DÃ©pendances :** Ce handler requiert la librairie `requests` (`pip install requests`).

#### Options

| ParamÃ¨tre | Type | DÃ©faut | Description |
|-----------|------|---------|-------------|
| `level` | `int` | `NOTSET` | Le niveau de log minimum requis. |
| `formatter`| `CustomFormatter`| `None` | Formateur (utile pour les rÃ¨gles). |
| `bot_token` | `str` | `None` | **Requis.** Token du bot Discord. |
| `guild_id` | `str` | `None` | ID du serveur Discord (alternative Ã  `guild_name`). |
| `guild_name` | `str` | `None` | Nom du serveur Discord (alternative Ã  `guild_id`). |
| `channel_id` | `str` | `None` | ID du channel Discord (alternative Ã  `channel_name`). |
| `channel_name` | `str` | `None` | Nom du channel Discord (alternative Ã  `channel_id`). Pour les DM, optionnel. |
| `user_id` | `str` | `None` | ID de l'utilisateur pour un message privÃ© (alternative Ã  `user_name`). |
| `user_name` | `str` | `None` | Nom d'utilisateur Discord pour un message privÃ© (alternative Ã  `user_id`). Requiert `guild_id` ou `guild_name`. |
| `content` | `str` | `None` | Template Jinja2 pour le contenu du message simple. |
| `embeds` | `list` | `None` | Liste d'embeds avec templates Jinja2. Chaque embed peut contenir : `title`, `description`, `color` (hex ou dÃ©cimal), `fields`, `footer`, `author`, `image`, `thumbnail`, `timestamp` (boolÃ©en). |
| `ssl_verify`|`bool` | `True` | Si `False`, dÃ©sactive la vÃ©rification du certificat SSL. |
| `ops` | `str` | `'>='` | OpÃ©rateur de comparaison pour le niveau. |
| `async_mode` | `bool` | `False` | Si `True`, l'envoi se fait dans un thread sÃ©parÃ©. |

#### Usage

##### 1. Programmatic (dans un script Python)

**Message simple :**
```python
from zpp_logs.core import Logger, CustomFormatter
from zpp_logs.handlers.discord import DiscordHandler
from zpp_logs.levels import ERROR

discord_handler = DiscordHandler(
    level=ERROR,
    formatter=CustomFormatter(""),
    bot_token="YOUR_BOT_TOKEN",
    guild_name="Mon Serveur",
    channel_name="logs",
    content="ðŸ”´ {{ levelname }} : {{ msg }}"
)

logger = Logger(name="discord_app", handlers=[discord_handler])
logger.error("Une erreur s'est produite.")
```

Avec embeds :

```python
embeds_config = [
    {
        "title": "Erreur dÃ©tectÃ©e",
        "description": "{{ msg }}",
        "color": "#E74C3C",  # Rouge en hex
        "fields": [
            {
                "name": "Niveau",
                "value": "{{ levelname }}",
                "inline": True
            },
            {
                "name": "Logger",
                "value": "{{ name }}",
                "inline": True
            }
        ],
        "footer": {
            "text": "zpp_logs"
        },
        "timestamp": True
    }
]

discord_handler = DiscordHandler(
    level=ERROR,
    formatter=CustomFormatter(""),
    bot_token="YOUR_BOT_TOKEN",
    guild_name="Mon Serveur",
    channel_name="alerts",
    content="Nouvelle alerte",
    embeds=embeds_config
)
```

Message privÃ© :


```python
discord_handler = DiscordHandler(
    level=ERROR,
    formatter=CustomFormatter(""),
    bot_token="YOUR_BOT_TOKEN",
    guild_name="Mon Serveur",
    user_name="admin_user",
    content="{{ levelname }}: {{ msg }}"
)
```

##### 2. DÃ©claratif (dans `config.yaml`)

Message simple :


```python
formatters:
  discord_format:
    format: ""

handlers:
  discord_simple:
    class: zpp_logs.DiscordHandler
    level: INFO
    formatter: discord_format
    bot_token: "YOUR_BOT_TOKEN"
    guild_name: "Mon Serveur"
    channel_name: "logs"
    content: "{{ levelname }} - {{ msg }}"

loggers:
  root:
    handlers: [discord_simple]
```

Avec embeds et couleurs hex :

```python
handlers:
  discord_embeds:
    class: zpp_logs.DiscordHandler
    level: ERROR
    formatter: discord_format
    bot_token: "YOUR_BOT_TOKEN"
    guild_name: "Mon Serveur"
    channel_name: "alerts"
    content: "Nouvelle alerte"
    embeds:
      - title: "ðŸ”´ {{ levelname }}"
        description: "{{ msg }}"
        color: "E74C3C"  # Rouge en hex (avec ou sans #)
        fields:
          - name: "Module"
            value: "{{ name }}"
            inline: true
          - name: "Niveau"
            value: "{{ levelno }}"
            inline: true
        footer:
          text: "zpp_logs Discord Handler"
        timestamp: true
```

Message privÃ© :

```python
handlers:
  discord_dm:
    class: zpp_logs.DiscordHandler
    level: CRITICAL
    formatter: discord_format
    bot_token: "YOUR_BOT_TOKEN"
    guild_name: "Mon Serveur"
    user_name: "admin_user"
    content: "ALERTE CRITIQUE: {{ msg }}"

loggers:
  root:
    handlers: [discord_dm]
```


### Journalisation Asynchrone

Pour les handlers qui peuvent Ãªtre lents (comme l'envoi d'e-mails avec `SMTPHandler` ou l'Ã©criture dans une base de donnÃ©es distante), `zpp_logs` offre un mode de journalisation asynchrone. Lorsqu'il est activÃ©, le handler s'exÃ©cute dans un thread d'arriÃ¨re-plan, ce qui empÃªche votre application principale de se bloquer.

Pour activer le mode asynchrone pour un handler, ajoutez simplement `async_mode: true` Ã  sa configuration dans votre fichier `config.yaml`.

**Exemple : Rendre le `SMTPHandler` asynchrone**

```yaml
# Exemple de SMTPHandler asynchrone dans config.yaml
handlers:
    email_critical_async:
        class: zpp_logs.SMTPHandler
        level: zpp_logs.CRITICAL
        async_mode