Metadata-Version: 2.4
Name: zpp_logs
Version: 2.0.0
Summary: Module de gestion de logs
Home-page: https://github.com/ZephyrOff/zpp_logs
Author: ZephyrOff <contact@apajak.fr>
License: MIT License
Project-URL: Documentation, https://github.com/ZephyrOff/zpp_logs
Keywords: logs module zephyroff
Platform: ALL
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: psutil
Requires-Dist: impmagic
Requires-Dist: zpp_color
Requires-Dist: pyyaml
Requires-Dist: jinja2
Requires-Dist: requests
Requires-Dist: SQLAlchemy
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: platform
Dynamic: project-url
Dynamic: requires-dist
Dynamic: summary

# 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.

### 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

Ã‰crit les logs dans la console (sortie standard ou erreur standard).

*   **`output`** : SpÃ©cifie la destination de la sortie. Peut Ãªtre `sys.stdout` (par dÃ©faut) ou `sys.stderr`.

    ```yaml
    # Exemple de ConsoleHandler dans config.yaml
    handlers:
        console_stdout:
            class: zpp_logs.ConsoleHandler
            level: zpp_logs.INFO
            ops: ">="
            formatter: standard
            filters:
                - "'secret' not in msg" # Filtre les messages contenant le mot 'secret'
            output: sys.stdout # Logs vers la sortie standard
        console_stderr:
            class: zpp_logs.ConsoleHandler
            level: zpp_logs.ERROR
            ops: ">="
            formatter: standard
            output: sys.stderr # Logs d'erreur vers la sortie d'erreur standard
    ```

#### FileHandler

Ã‰crit les logs dans un fichier, avec des options avancÃ©es de rotation.

*   **`filename`** : Le chemin du fichier de log. Peut inclure des expressions Jinja2 pour des noms de fichiers dynamiques (ex: par date).
*   **`maxBytes`** : La taille maximale du fichier de log en octets avant rotation. Si `0`, la taille n'est pas limitÃ©e.
*   **`backupCount`** : Le nombre de fichiers de backup Ã  conserver aprÃ¨s rotation.
    *   Si `backupCount > 0` : Rotation standard. Quand le fichier atteint `maxBytes`, il est renommÃ© (ex: `app.log.1`, `app.log.2`, etc.) et un nouveau fichier est crÃ©Ã©. Les anciens backups sont supprimÃ©s si leur nombre dÃ©passe `backupCount`.
    *   Si `backupCount == 0` et `maxBytes > 0` : Logging circulaire. Quand le fichier atteint `maxBytes`, la plus ancienne ligne est supprimÃ©e pour faire de la place Ã  la nouvelle. Le fichier ne grossit jamais au-delÃ  de `maxBytes`.

    ```yaml
    # Exemple de FileHandler dans config.yaml
    handlers:
        file_daily:
            class: zpp_logs.FileHandler
            level: zpp_logs.INFO
            formatter: standard
            filename: "logs/app_{{ date('%Y-%m-%d') }}.log" # Nom de fichier quotidien
            maxBytes: 0 # Pas de limite de taille
            backupCount: 0 # Pas de rotation
        file_rotated:
            class: zpp_logs.FileHandler
            level: zpp_logs.INFO
            formatter: standard
            filename: "logs/rotated_app.log"
            maxBytes: 1048576 # 1 MB
            backupCount: 5 # Garde 5 fichiers de backup (rotated_app.log.1, .2, etc.)
        file_circular:
            class: zpp_logs.FileHandler
            level: zpp_logs.DEBUG
            formatter: standard
            filename: "logs/circular_debug.log"
            maxBytes: 524288 # 512 KB
            backupCount: 0 # Active le logging circulaire
    ```

#### DatabaseHandler

Enregistre les logs dans une base de donnÃ©es (supporte SQLite, MySQL, etc.).

*   **`model`** : (Optionnel) Permet de spÃ©cifier un modÃ¨le de table SQLAlchemy. C'est la mÃ©thode recommandÃ©e pour un contrÃ´le prÃ©cis du schÃ©ma de la table.
    *   En configuration YAML, fournissez le chemin d'importation du modÃ¨le : `model: 'mon_app.models.LogEntry'`.
    *   En configuration programmatique, passez directement la classe du modÃ¨le : `model=LogEntry`.
    *   Si un modÃ¨le est fourni, le handler utilisera son schÃ©ma pour crÃ©er la table (si elle n'existe pas). Les paramÃ¨tres `table` et `columns` deviennent alors facultatifs.

*   **`connector`** : Un dictionnaire spÃ©cifiant les dÃ©tails de connexion Ã  la base de donnÃ©es.
    *   `engine` : Le type de base de donnÃ©es (ex: `sqlite`, `mysql`).
    *   Pour `sqlite` : `filename` (chemin du fichier de base de donnÃ©es).
    *   Pour `mysql` : `host`, `user`, `password`, `database`.
    *   `table` : Le nom de la table oÃ¹ les logs seront stockÃ©s (par dÃ©faut: `logs`).
*   **`columns`** : (Optionnel) Un dictionnaire pour mapper les attributs du record de log aux noms de colonnes de la table. Les valeurs peuvent Ãªtre des expressions Jinja2. Si non spÃ©cifiÃ©, des colonnes par dÃ©faut (`timestamp`, `level`, `logger_name`, `message`) sont utilisÃ©es.

    ```yaml
    # Exemple de DatabaseHandler dans config.yaml
    handlers:
        db_sqlite:
            class: zpp_logs.DatabaseHandler
            level: zpp_logs.INFO
            formatter: standard
            connector:
                engine: sqlite
                filename: "logs/app_logs.db"
                table: "application_events"
            columns: # Mappage personnalisÃ© des colonnes
                event_time: "date('%Y-%m-%d %H:%M:%S')" # Renomme 'timestamp' en 'event_time'
                log_level: "levelname" # Renomme 'level' en 'log_level'
                source: "name" # Renomme 'logger_name' en 'source'
                full_message: "msg" # Renomme 'message' en 'full_message'
                user_id: "user_id if 'user_id' in record else 'N/A'" # Ajoute une colonne conditionnelle
        db_mysql:
            class: zpp_logs.DatabaseHandler
            level: zpp_logs.WARNING
            formatter: standard
            connector:
                engine: mysql
                host: localhost
                user: loguser
                password: logpassword
                database: myapp_logs_db
            # Utilise les colonnes par dÃ©faut si 'columns' n'est pas spÃ©cifiÃ©

**Exemple avec un modÃ¨le SQLAlchemy**

1.  **DÃ©finissez votre modÃ¨le SQLAlchemy :**

    ```python
    # dans un fichier models.py
    from sqlalchemy.orm import declarative_base
    from sqlalchemy import Column, Integer, String, DateTime
    from datetime import datetime

    Base = declarative_base()

    class LogEntry(Base):
        __tablename__ = 'log_entries'
        id = Column(Integer, primary_key=True)
        timestamp = Column(DateTime, default=datetime.utcnow)
        level = Column(String(50))
        message = Column(String)
        logger_name = Column(String(100))
        user_id = Column(Integer) # Exemple de colonne personnalisÃ©e
    ```

2.  **Configurez le handler pour utiliser ce modÃ¨le :**

    *En `config.yaml` :*
    ```yaml
    handlers:
        db_with_model:
            class: zpp_logs.DatabaseHandler
            level: zpp_logs.INFO
            formatter: standard
            connector:
                engine: sqlite
                filename: "logs/app_with_model.db"
            model: 'models.LogEntry' # Chemin d'importation du modÃ¨le
    ```

    *En Python :*
    ```python
    # from models import LogEntry
    db_handler_model = DatabaseHandler(
        level=INFO,
        formatter=standard_formatter,
        connector={"engine": "sqlite", "filename": "logs/app_with_model.db"},
        model=LogEntry # Passez la classe du modÃ¨le directement
    )
    ```
    ```

#### SMTPHandler

Envoie les logs par e-mail via un serveur SMTP. IdÃ©al pour les alertes critiques.

*   **`host`** : L'adresse du serveur SMTP.
*   **`port`** : Le port du serveur SMTP (souvent 25, 587 pour TLS, 465 pour SSL).
*   **`username`** : Nom d'utilisateur pour l'authentification SMTP.
*   **`password`** : Mot de passe pour l'authentification SMTP.
*   **`fromaddr`** : L'adresse e-mail de l'expÃ©diteur.
*   **`toaddrs`** : Une liste d'adresses e-mail des destinataires.
*   **`subject`** : Le sujet de l'e-mail. Peut inclure des expressions Jinja2 pour des sujets dynamiques.

    ```yaml
    # Exemple de SMTPHandler dans config.yaml
    handlers:
        email_critical:
            class: zpp_logs.SMTPHandler
            level: zpp_logs.CRITICAL
            formatter: standard
            host: smtp.your-email-provider.com
            port: 587
            username: your_email@example.com
            password: your_email_password
            fromaddr: "no-reply@your-app.com"
            toaddrs: ["admin@your-app.com", "devops@your-app.com"]
            subject: "ALERTE CRITIQUE: {{ levelname }} dans {{ name }} Ã  {{ date('%Y-%m-%d %H:%M:%S') }}"
    ```

#### ResendHandler

Envoie les logs par e-mail en utilisant l'API Resend. NÃ©cessite une clÃ© API Resend.

*   **`api_key`** : Votre clÃ© API Resend (commence par `re_`).
*   **`fromaddr`** : L'adresse e-mail de l'expÃ©diteur (doit Ãªtre un domaine vÃ©rifiÃ© dans Resend).
*   **`to`** : Une liste d'adresses e-mail des destinataires.
*   **`subject`** : Le sujet de l'e-mail. Peut inclure des expressions Jinja2.

    ```yaml
    # Exemple de ResendHandler dans config.yaml
    handlers:
        email_resend:
            class: zpp_logs.ResendHandler
            level: zpp_logs.ERROR
            formatter: standard
            api_key: re_YOUR_RESEND_API_KEY_HERE # Remplacez par votre vraie clÃ© API
            fromaddr: "onboarding@your-verified-domain.com"
            to: ["support@your-app.com"]
            subject: "ERREUR APPLICATION: {{ levelname }} dÃ©tectÃ©e dans {{ name }}"
    ```

### 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: true  # Active le mode asynchrone
        formatter: standard
        host: smtp.your-email-provider.com
        port: 587
        username: your_email@example.com
        password: your_email_password
        fromaddr: "no-reply@your-app.com"
        toaddrs: ["admin@your-app.com"]
        subject: "ALERTE CRITIQUE (Async): {{ levelname }} dans {{ name }}"
```

C'est tout ! Ce handler enverra maintenant les e-mails en arriÃ¨re-plan sans ralentir votre application. Cette fonctionnalitÃ© peut Ãªtre appliquÃ©e Ã  n'importe quel handler.

De mÃªme, lors de la crÃ©ation d'un handler en Python, vous pouvez l'activer en passant `async_mode=True` Ã  son constructeur.

**Exemple : Rendre le `ConsoleHandler` asynchrone en Python**

```python
# Exemple de ConsoleHandler asynchrone en Python
mon_handler_console = ConsoleHandler(
    level=INFO,
    formatter=mon_formatter,
    async_mode=True  # Active le mode asynchrone
)

logger = Logger(name="mon_logger", handlers=[mon_handler_console])
logger.info("Ce message sera traitÃ© en arriÃ¨re-plan.")
print("Ce print s'exÃ©cute immÃ©diatement.")
```

### 2. Configuration Programmatique

La configuration programmatique vous permet de construire et de gÃ©rer votre systÃ¨me de logging directement dans votre code Python, offrant une flexibilitÃ© maximale et un contrÃ´le prÃ©cis sur chaque composant.

#### Imports NÃ©cessaires

Pour commencer, importez les classes et constantes essentielles :

```python
from zpp_logs import (
    Logger, CustomFormatter, ConsoleHandler, FileHandler, DatabaseHandler,
    SMTPHandler, ResendHandler,
    DEBUG, INFO, WARNING, ERROR, CRITICAL, SUCCESS
)
import sys
import os
from sqlalchemy import create_engine, text # Pour la vÃ©rification de la base de donnÃ©es
```

#### 2.1. CrÃ©ation d'un Formatter

Un formatter dÃ©finit l'apparence de vos messages de log. Vous pouvez spÃ©cifier un format de base avec `format_str` (supportant Jinja2) et ajouter des rÃ¨gles de formatage dynamiques.

```python
# CrÃ©ation d'un formatter de base
programmatic_formatter = CustomFormatter(
    format_str="[PROG] {{ date('%H:%M:%S') }} | {{ levelname }} | {{ msg }}"
)

# Ajout de rÃ¨gles dynamiques au formatter
# Ces rÃ¨gles modifient l'apparence des champs 'levelname' ou 'msg' en fonction de conditions
programmatic_formatter.set_rule(
    "levelname == 'INFO'",
    {"levelname": "{{ fg('cyan') }}INFO{{ attr(0) }}"}
)
programmatic_formatter.set_rule(
    "levelname == 'WARNING'",
    {"msg": "{{ fg('yellow') }}WARNING: {{ msg }}{{ attr(0) }}"}
)
programmatic_formatter.set_rule(
    "__default__",
    {"levelname": "{{ fg('magenta') }}PROG_DEFAULT{{ attr(0) }}"}
)
```

#### 2.2. CrÃ©ation des Handlers

Les handlers dirigent les messages de log formatÃ©s vers diffÃ©rentes destinations. Chaque handler est configurÃ© avec un niveau minimum, un opÃ©rateur de comparaison, un formatter et des filtres optionnels.

##### ConsoleHandler

Envoie les logs Ã  la console (stdout ou stderr).

```python
# ConsoleHandler: envoie les logs INFO et supÃ©rieurs Ã  la sortie standard
programmatic_console_handler = ConsoleHandler(
    level=INFO,
    formatter=programmatic_formatter,
    output=sys.stdout
)
```

##### FileHandler

Ã‰crit les logs dans un fichier, avec des options de rotation avancÃ©es.

```python
# FileHandler: Ã©crit les logs DEBUG et supÃ©rieurs dans un fichier avec rotation
programmatic_file_handler = FileHandler(
    level=DEBUG,
    formatter=programmatic_formatter,
    filename="logs/programmatic_app.log",
    maxBytes=512,
    backupCount=1
)
```

##### DatabaseHandler

Enregistre les logs dans une base de donnÃ©es. Supporte SQLite, MySQL, etc., avec mappage de colonnes personnalisable.

```python
# DatabaseHandler: enregistre les logs INFO et supÃ©rieurs dans une base SQLite
# Assurez-vous que le fichier DB n'existe pas pour un test propre
if os.path.exists("logs/programmatic_db.db"): os.remove("logs/programmatic_db.db")
programmatic_db_handler = DatabaseHandler(
    level=INFO,
    formatter=programmatic_formatter,
    connector={
        "engine": "sqlite",
        "filename": "logs/programmatic_db.db",
        "table": "prog_logs"
    },
    columns={
        "timestamp": "date('%Y-%m-%d %H:%M:%S')",
        "level": "levelname",
        "message": "msg"
    }
)
```

##### SMTPHandler

Envoie les logs par e-mail via un serveur SMTP.

```python
# SMTPHandler: envoie les logs CRITICAL et supÃ©rieurs par e-mail
programmatic_smtp_handler = SMTPHandler(
    level=CRITICAL,
    formatter=programmatic_formatter,
    host="smtp.mailtrap.io",
    port=2525,
    username="your_mailtrap_username",
    password="your_mailtrap_password",
    fromaddr="programmatic@example.com",
    toaddrs=["admin@programmatic.com"],
    subject="[PROG] ALERTE CRITIQUE: {{ levelname }} de {{ name }}"
)
```

##### ResendHandler

Envoie les logs par e-mail via l'API Resend.

```python
# ResendHandler: envoie les logs ERROR et supÃ©rieurs via l'API Resend
programmatic_resend_handler = ResendHandler(
    level=ERROR,
    formatter=programmatic_formatter,
    api_key="re_YOUR_RESEND_API_KEY",
    fromaddr="onboarding@programmatic.dev",
    to=["dev@programmatic.dev"],
    subject="[PROG] ERREUR: {{ levelname }} de {{ name }}"
)
```

#### 2.3. CrÃ©ation du Logger

Un logger est le point d'entrÃ©e pour enregistrer vos messages. Il regroupe un ensemble de handlers.

```python
# CrÃ©ation d'un logger et association des handlers
programmatic_logger = Logger(name="programmatic_logger", handlers=[
    programmatic_console_handler,
    programmatic_file_handler,
    programmatic_db_handler,
    programmatic_smtp_handler,
    programmatic_resend_handler
])
```

#### 2.4. Utilisation du Logger

Une fois configurÃ©, utilisez le logger pour enregistrer vos messages.

```python
# Enregistrement de messages de diffÃ©rents niveaux
programmatic_logger.info("Ceci est un message info du logger programmatique.")
programmatic_logger.warning("Ceci est un message d'avertissement du logger programmatique.")
programmatic_logger.error("Ceci est un message d'erreur du logger programmatique.")
programmatic_logger.info("Ce message contient des informations secrÃ¨tes du logger programmatique.") # Devrait Ãªtre filtrÃ© par le ConsoleHandler
programmatic_logger.critical("Ceci est un message critique qui devrait envoyer un e-mail.")

# Exemple de vÃ©rification: lecture des logs depuis la base de donnÃ©es
prog_db_engine = create_engine("sqlite:///logs/programmatic_db.db")
with prog_db_engine.connect() as conn:
    result = conn.execute(text("SELECT timestamp, level, message FROM prog_logs ORDER BY timestamp DESC LIMIT 3"))
    print("\n--- Derniers 3 logs de la DB programmatique ---")
    for row in result:
        print(f"Timestamp: {row.timestamp}, Level: {row.level}, Message: {row.message}")
```


## Modification Dynamique

Les objets `Logger`, `CustomFormatter` et les instances de `BaseHandler` (et ses sous-classes) exposent des mÃ©thodes pour modifier leur comportement aprÃ¨s leur crÃ©ation.

### Modification des Formatters

```python
# Supposons 'my_formatter' est une instance de CustomFormatter
# my_formatter = CustomFormatter(...)

# Ajouter/Modifier une rÃ¨gle
my_formatter.set_rule(
    "levelname == 'ERROR'",
    {"levelname": "{{ fg('red') }}DYNAMIC_ERROR{{ attr(0) }}"}
)

# Supprimer une rÃ¨gle
my_formatter.delete_rule("levelname == 'WARNING'")

# Les logs suivants utiliseront les rÃ¨gles modifiÃ©es
# programmatic_logger.error("Un message d'erreur dynamique.")
```

### Modification des Handlers

```python
# Supposons 'my_handler' est une instance de ConsoleHandler, FileHandler, etc.
# my_handler = ConsoleHandler(...)

# Changer le niveau
my_handler.set_level(DEBUG)

# Changer l'opÃ©rateur de comparaison
my_handler.set_ops('==') # N'acceptera que les messages de niveau DEBUG

# Ajouter un filtre
my_handler.add_filter("'nouvel_element' not in msg")

# Supprimer un filtre
my_handler.remove_filter("'secret' not in msg")

# Changer le formatter (si le handler a Ã©tÃ© crÃ©Ã© avec un formatter)
# my_handler.set_formatter(un_autre_formatter)
```

### Modification des Loggers

```python
# Supposons 'my_logger' est une instance de Logger
# my_logger = Logger(...)

# Ajouter un handler
my_logger.add_handler(programmatic_file_handler)

# Supprimer un handler
my_logger.remove_handler(programmatic_console_handler)
```

## Fonctions jinja2 Ã©tendues

*   **Fonctions Jinja2 personnalisÃ©es** : Ces fonctions peuvent Ãªtre utilisÃ©es directement dans votre chaÃ®ne de formatage ou dans les rÃ¨gles. Elles permettent d'accÃ©der Ã  des informations contextuelles trÃ¨s riches.

    *   **Formatage et Attributs :**
        *   `fg(color_name)` : Applique une couleur de premier plan au texte suivant. `color_name` peut Ãªtre `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, ou des noms spÃ©cifiques comme `deep_sky_blue_3a` (cyan), `medium_purple_4` (magenta), `grey_46` (blanc).
            *   Exemple : "{{ fg('green') }}Mon message en vert{{ attr(0) }}"
        *   `bg(color_name)` : Applique une couleur d'arriÃ¨re-plan au texte suivant. Utilise les mÃªmes `color_name` que `fg()`.
            *   Exemple : "{{ bg('blue') }}Texte sur fond bleu{{ attr(0) }}"
        *   `attr(code)` : Applique des attributs de texte. Le code `0` rÃ©initialise tous les styles (couleur, gras, etc.).
            *   Exemple : "{{ fg('red') }}Texte rouge{{ attr(0) }} Texte normal"

    *   **Date et Heure :**
        *   `date(format_str)` : Formate la date et l'heure actuelles. `format_str` suit les codes de formatage de `strftime` de Python (ex: `%Y-%m-%d %H:%M:%S`).
            *   Exemple : "Log du {{ date('%Y-%m-%d %H:%M') }}"
        *   `epoch()` : Renvoie le timestamp Unix actuel (nombre de secondes depuis l'Ã©poque).
            *   Exemple : "Timestamp: {{ epoch() }}"

    *   **Informations sur le Code Source :**
        *   `exc_info()` : Renvoie les informations sur l'exception courante (type, valeur, traceback). Utile dans les blocs `try...except`.
            *   Exemple : "Exception: {{ exc_info() }}"
        *   `filename()` : Renvoie le nom du fichier Python oÃ¹ l'appel de log a Ã©tÃ© effectuÃ©.
            *   Exemple : "Fichier: {{ filename() }}"
        *   `filepath()` : Renvoie le chemin absolu du rÃ©pertoire contenant le fichier Python oÃ¹ l'appel de log a Ã©tÃ© effectuÃ©.
            *   Exemple : "Chemin du fichier: {{ filepath() }}"
        *   `lineno()` : Renvoie le numÃ©ro de ligne dans le fichier Python oÃ¹ l'appel de log a Ã©tÃ© effectuÃ©.
            *   Exemple : "Ligne: {{ lineno() }}"
        *   `functname()` : Renvoie le nom de la fonction ou mÃ©thode Python oÃ¹ l'appel de log a Ã©tÃ© effectuÃ©.
            *   Exemple : "Fonction: {{ functname() }}"

    *   **Informations sur le SystÃ¨me de Fichiers / Chemin :**
        *   `path()` : Renvoie le chemin absolu du rÃ©pertoire de travail actuel.
            *   Exemple : "CWD: {{ path() }}"

    *   **Informations sur le Processus :**
        *   `process()` : Renvoie le nom du processus Python actuel.
            *   Exemple : "Processus: {{ process() }}"
        *   `processid()` : Renvoie l'ID du processus Python actuel.
            *   Exemple : "PID: {{ processid() }}"

    *   **Informations sur l'Utilisateur :**
        *   `username()` : Renvoie le nom d'utilisateur du systÃ¨me.
            *   Exemple : "Utilisateur: {{ username() }}"
        *   `uid()` : Renvoie l'ID utilisateur (UID) du systÃ¨me.
            *   Exemple : "UID: {{ uid() }}"

    *   **Informations sur le SystÃ¨me d'Exploitation :**
        *   `os_name()` : Renvoie le nom du systÃ¨me d'exploitation (ex: `Windows`, `Linux`, `Darwin`).
            *   Exemple : "OS: {{ os_name() }}"
        *   `os_version()` : Renvoie la version dÃ©taillÃ©e du systÃ¨me d'exploitation.
            *   Exemple : "Version OS: {{ os_version() }}"
        *   `os_release()` : Renvoie la version du systÃ¨me d'exploitation (ex: `10`, `20.04`).
            *   Exemple : "Release OS: {{ os_release() }}"
        *   `platform()` : Renvoie une chaÃ®ne d'identification de la plateforme (ex: `Windows-10-10.0.19045-SP0`).
            *   Exemple : "Plateforme: {{ platform() }}"
        *   `os_archi()` : Renvoie l'architecture du systÃ¨me d'exploitation (ex: `64bit`).
            *   Exemple : "Architecture OS: {{ os_archi() }}"

    *   **Informations sur la MÃ©moire (RAM) :**
        *   `mem_total()` : MÃ©moire RAM totale du systÃ¨me.
        *   `mem_available()` : MÃ©moire RAM disponible.
        *   `mem_used()` : MÃ©moire RAM utilisÃ©e.
        *   `mem_free()` : MÃ©moire RAM libre.
        *   `mem_percent()` : Pourcentage de mÃ©moire RAM utilisÃ©e.
            *   Exemple : "RAM: {{ mem_used() }} / {{ mem_total() }} ({{ mem_percent() }}%)"

    *   **Informations sur la MÃ©moire Swap :**
        *   `swap_total()` : MÃ©moire Swap totale.
        *   `swap_used()` : MÃ©moire Swap utilisÃ©e.
        *   `swap_free()` : MÃ©moire Swap libre.
        *   `swap_percent()` : Pourcentage de mÃ©moire Swap utilisÃ©e.
            *   Exemple : "Swap: {{ swap_used() }} / {{ swap_total() }} ({{ swap_percent() }}%)"

    *   **Informations sur le CPU :**
        *   `cpu_count()` : Nombre de cÅ“urs physiques du CPU.
        *   `cpu_logical_count()` : Nombre de cÅ“urs logiques (incluant les threads) du CPU.
        *   `cpu_percent()` : Pourcentage d'utilisation du CPU (sur un court intervalle).
            *   Exemple : "CPU: {{ cpu_percent() }}% ({{ cpu_logical_count() }} cÅ“urs)"

    *   **Informations sur le Disque Actuel (du fichier de log) :**
        *   `current_disk_device()` : Nom du pÃ©riphÃ©rique de disque (ex: `C:\`).
        *   `current_disk_mountpoint()` : Point de montage du disque.
        *   `current_disk_fstype()` : Type de systÃ¨me de fichiers (ex: `NTFS`).
        *   `current_disk_total()` : Taille totale du disque.
        *   `current_disk_used()` : Espace utilisÃ© sur le disque.
        *   `current_disk_free()` : Espace libre sur le disque.
        *   `current_disk_percent()` : Pourcentage d'utilisation du disque.
            *   Exemple : "Disque ({{ current_disk_device() }}): {{ current_disk_used() }} / {{ current_disk_total() }} ({{ current_disk_percent() }}%)"

    *   **Fonctions Utilitaires :**
        *   `re_match(regex_pattern, value)` : Tente de faire correspondre `regex_pattern` au dÃ©but de `value`. Renvoie un objet match si trouvÃ©, `None` sinon.
            *   Exemple : "Match: {{ re_match('^(Error|Warning)', levelname) is not none }}"


## ExtensibilitÃ©

Le module est conÃ§u pour Ãªtre facilement extensible :
*   **Nouveaux Handlers** : CrÃ©ez de nouvelles classes hÃ©ritant de `BaseHandler` et implÃ©mentez la mÃ©thode `emit`. Ajoutez-les Ã  `_handler_class_map`.
*   **Nouvelles Fonctions Jinja2** : DÃ©finissez de nouvelles fonctions Python et ajoutez-les aux `globals` des environnements Jinja2 (`env`, `filter_env`, `filename_env`, etc.) oÃ¹ vous souhaitez les utiliser.
*   **Nouveaux Types de Colonnes DB** : Ã‰tendez `DatabaseHandler` pour supporter plus de types de colonnes SQLAlchemy.

---
