Skip to content

Commit 3a64f29

Browse files
committed
First commit...
1 parent 781551c commit 3a64f29

File tree

5 files changed

+187
-176
lines changed

5 files changed

+187
-176
lines changed

Makefile

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ PIP = $(VENV_BIN)/pip
1818
.DEFAULT_GOAL := help
1919

2020
# Déclare les cibles qui ne sont pas des fichiers
21-
.PHONY: help install-dev install run clean clean-venv lint format
21+
.PHONY: help install-dev install run clean clean-venv
2222

2323
# =============================================================================
2424
# CIBLES PRINCIPALES
@@ -32,7 +32,7 @@ install: venv ## 📦 Installe la librairie en mode éditable
3232
@echo "--- Installation des dépendances du projet ---"
3333
@$(PIP) install -e .
3434

35-
install-dev: venv ## 🛠️ Installe les outils de développement (lint, formatage...)
35+
install-dev: venv ## 🛠️ Installe les outils de développement...
3636
@echo "--- Installation des dépendances de développement ---"
3737
# Note : nécessite une section [project.optional-dependencies] dans pyproject.toml
3838
@$(PIP) install -e ".[dev]"
@@ -41,18 +41,6 @@ run: install ## ▶️ Lance le script d'exemple
4141
@echo "--- Lancement du script d'exemple (examples/main.py) ---"
4242
@$(VENV_BIN)/python examples/main.py
4343

44-
# =============================================================================
45-
# CIBLES DE QUALITÉ & NETTOYAGE
46-
# =============================================================================
47-
48-
lint: install-dev ## 🔬 Analyse le code avec un linter (ex: ruff)
49-
@echo "--- Analyse du code avec le linter ---"
50-
@$(VENV_BIN)/ruff check src examples
51-
52-
format: install-dev ## 💅 Formate le code automatiquement (ex: black, ruff)
53-
@echo "--- Formatage du code ---"
54-
@$(VENV_BIN)/ruff format src examples
55-
5644
clean: clean-venv ## 🧹 Nettoie tous les fichiers générés (cache, etc.)
5745
@echo "--- Nettoyage des fichiers cache Python ---"
5846
@find . -type f -name "*.py[co]" -delete

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ Utilisez `make` pour automatiser les tâches courantes.
101101
* `make` ou `make help` : Affiche toutes les commandes disponibles.
102102
* `make install` : Installe l'environnement de développement et les dépendances.
103103
* `make run` : Lance le script d'exemple `examples/main.py`.
104-
* `make lint` : Analyse la qualité du code avec `ruff`.
105-
* `make format` : Formate le code automatiquement.
106104
* `make clean` : Supprime l'environnement virtuel et tous les fichiers temporaires.
107105

108106
## 📚 Composants

src/pubsub/base_logger.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import datetime
2+
import os
3+
import queue
4+
import threading
5+
from abc import ABC, abstractmethod
6+
from typing import Optional, Dict, Any
7+
8+
9+
class BaseBusinessLogger(ABC):
10+
"""
11+
Classe de base abstraite pour les loggers métier.
12+
Gère toute la logique commune : singleton, initialisation paresseuse,
13+
file d'attente, thread de travail, et gestion de contexte 'with'.
14+
"""
15+
_instance = None
16+
_lock = threading.Lock()
17+
18+
_GREEN = "\033[92m"
19+
_RESET = "\033[0m"
20+
21+
def __new__(cls, *args, **kwargs):
22+
if not cls._instance:
23+
with cls._lock:
24+
if not cls._instance:
25+
cls._instance = super().__new__(cls)
26+
return cls._instance
27+
28+
def __init__(self):
29+
if not hasattr(self, '_initialized_flag'):
30+
self._initialized_flag = False
31+
self.is_enabled = False
32+
self._init_lock = threading.Lock()
33+
34+
# --- Méthodes et propriétés abstraites à implémenter par les enfants ---
35+
36+
@property
37+
@abstractmethod
38+
def logger_name(self) -> str:
39+
"""Nom du logger pour les messages console."""
40+
pass
41+
42+
@property
43+
@abstractmethod
44+
def enabled_env_var(self) -> str:
45+
"""Clé de la variable d'environnement pour activer le logger."""
46+
pass
47+
48+
@property
49+
@abstractmethod
50+
def db_file_env_var(self) -> str:
51+
"""Clé de la variable d'environnement pour le fichier de BDD."""
52+
pass
53+
54+
@abstractmethod
55+
def _setup_backend(self, db_file: str) -> bool:
56+
"""Prépare le backend de stockage (crée le fichier, la table, etc.). Retourne True si succès."""
57+
pass
58+
59+
@abstractmethod
60+
def _write_log_to_backend(self, log_item: Any):
61+
"""Écrit un seul item de log dans le backend."""
62+
pass
63+
64+
@abstractmethod
65+
def _shutdown_backend(self):
66+
"""Ferme proprement les connexions au backend (ex: db.close())."""
67+
pass
68+
69+
# --- Logique commune ---
70+
71+
def _lazy_initialize(self):
72+
with self._init_lock:
73+
if self._initialized_flag:
74+
return
75+
76+
enabled = os.getenv(self.enabled_env_var, "false").lower() in ("true", "1", "yes")
77+
db_file = os.getenv(self.db_file_env_var)
78+
79+
if enabled and db_file:
80+
if self._setup_backend(db_file):
81+
self.is_enabled = True
82+
self.log_queue = queue.Queue()
83+
self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
84+
self.worker_thread.start()
85+
print(f"✅ {self.logger_name} auto-configuré. Logs dans '{db_file}'.")
86+
87+
self._initialized_flag = True
88+
89+
def _process_queue(self):
90+
while True:
91+
try:
92+
log_item = self.log_queue.get()
93+
if log_item is None: break
94+
self._write_log_to_backend(log_item)
95+
self.log_queue.task_done()
96+
except Exception as e:
97+
print(f"❌ Erreur dans le worker {self.logger_name} : {e}")
98+
99+
def log(self, event_type: str, details: Optional[Dict[str, Any]] = None):
100+
if not self._initialized_flag: self._lazy_initialize()
101+
if not self.is_enabled: return
102+
103+
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
104+
details_str = f"- {details}" if details else ""
105+
console_output = f"[{self.logger_name}] {event_type} {details_str}"
106+
print(f"{self._GREEN}{console_output}{self._RESET}")
107+
108+
self.log_queue.put({
109+
'timestamp': timestamp,
110+
'event_type': event_type,
111+
'details': details
112+
})
113+
114+
def shutdown(self, wait=True):
115+
if not self._initialized_flag: self._lazy_initialize()
116+
if not self.is_enabled or not hasattr(self, 'log_queue'): return
117+
if wait: self.log_queue.join()
118+
self.log_queue.put(None)
119+
if hasattr(self, 'worker_thread'): self.worker_thread.join(timeout=5)
120+
self._shutdown_backend()
121+
print(f"✅ {self.logger_name} arrêté proprement.")
122+
123+
def __enter__(self):
124+
return self
125+
126+
def __exit__(self, exc_type, exc_value, traceback):
127+
self.shutdown()

src/pubsub/json_business_logger.py

Lines changed: 30 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,44 @@
1-
import datetime
21
import os
3-
import queue
4-
import threading
5-
from typing import Optional, Dict, Any
2+
from typing import Optional
63

74
from tinydb import TinyDB
85

6+
from .base_logger import BaseBusinessLogger
97

10-
class JsonBusinessLogger:
11-
_instance = None
12-
_lock = threading.Lock()
13-
_GREEN = "\033[92m"
14-
_RESET = "\033[0m"
158

16-
def __new__(cls, *args, **kwargs):
17-
if not cls._instance:
18-
with cls._lock:
19-
if not cls._instance:
20-
cls._instance = super().__new__(cls)
21-
return cls._instance
9+
class JsonBusinessLogger(BaseBusinessLogger):
10+
# --- Définition des propriétés abstraites ---
11+
@property
12+
def logger_name(self) -> str:
13+
return "EVENT-JSON"
2214

23-
def __init__(self):
24-
if not hasattr(self, '_initialized_flag'):
25-
self._initialized_flag = False
26-
self.is_enabled = False
27-
self._init_lock = threading.Lock()
28-
self.db: Optional[TinyDB] = None
15+
@property
16+
def enabled_env_var(self) -> str:
17+
return "JSON_BUSINESS_LOGGER_ENABLED"
2918

30-
def _lazy_initialize(self):
31-
with self._init_lock:
32-
if self._initialized_flag: return
19+
@property
20+
def db_file_env_var(self) -> str:
21+
return "JSON_BUSINESS_LOGGER_DB_FILE"
3322

34-
enabled = os.getenv("JSON_BUSINESS_LOGGER_ENABLED", "false").lower() in ("true", "1", "yes")
35-
db_file = os.getenv("JSON_BUSINESS_LOGGER_DB_FILE")
23+
# --- Implémentation des méthodes abstraites ---
24+
def _setup_backend(self, db_file: str) -> bool:
25+
self.db: Optional[TinyDB] = None
26+
try:
27+
os.makedirs(os.path.dirname(db_file), exist_ok=True)
28+
self.db = TinyDB(db_file, indent=2, ensure_ascii=False)
29+
return True
30+
except Exception as e:
31+
print(f"❌ Erreur lors de l'initialisation de {self.logger_name} : {e}")
32+
return False
3633

37-
if enabled and db_file:
38-
try:
39-
os.makedirs(os.path.dirname(db_file), exist_ok=True)
40-
self.db = TinyDB(db_file, indent=2, ensure_ascii=False)
41-
self.is_enabled = True
42-
self.log_queue = queue.Queue()
43-
self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
44-
self.worker_thread.start()
45-
print(f"✅ JsonBusinessLogger auto-configuré. Logs dans '{db_file}'.")
46-
except Exception as e:
47-
print(f"❌ Erreur lors de l'initialisation de JsonBusinessLogger : {e}")
48-
self.is_enabled = False
34+
def _write_log_to_backend(self, log_item: dict):
35+
if self.db:
36+
self.db.insert(log_item)
4937

50-
self._initialized_flag = True
51-
52-
def _process_queue(self):
53-
while True:
54-
try:
55-
log_item = self.log_queue.get()
56-
if log_item is None: break
57-
if self.db: self.db.insert(log_item)
58-
self.log_queue.task_done()
59-
except Exception as e:
60-
print(f"❌ Erreur dans le worker JsonBusinessLogger : {e}")
61-
62-
def log(self, event_type: str, details: Optional[Dict[str, Any]] = None):
63-
if not self._initialized_flag: self._lazy_initialize()
64-
if not self.is_enabled: return
65-
66-
details_str = f"- {details}" if details else ""
67-
console_output = f"[EVENT-JSON] {event_type} {details_str}"
68-
print(f"{self._GREEN}{console_output}{self._RESET}")
69-
70-
log_document = {
71-
'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(),
72-
'event_type': event_type,
73-
'details': details
74-
}
75-
self.log_queue.put(log_document)
76-
77-
def shutdown(self, wait=True):
78-
if not self._initialized_flag: self._lazy_initialize()
79-
if not self.is_enabled or not hasattr(self, 'log_queue'): return
80-
if wait: self.log_queue.join()
81-
self.log_queue.put(None)
82-
if hasattr(self, 'worker_thread'): self.worker_thread.join(timeout=5)
83-
if self.db: self.db.close()
84-
print("✅ JsonBusinessLogger arrêté proprement.")
85-
86-
def __enter__(self):
87-
return self
88-
89-
def __exit__(self, exc_type, exc_value, traceback):
90-
self.shutdown()
38+
def _shutdown_backend(self):
39+
if self.db:
40+
self.db.close()
9141

9242

43+
# --- L'instance singleton reste la même ---
9344
json_business_logger = JsonBusinessLogger()

0 commit comments

Comments
 (0)