Skip to content

Commit b620209

Browse files
committed
First commit...
0 parents  commit b620209

File tree

6 files changed

+285
-0
lines changed

6 files changed

+285
-0
lines changed

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ----------------------------------------------------------------------------
2+
# BUSINESS EVENT LOGGER CONFIGURATION
3+
# ----------------------------------------------------------------------------
4+
# Enable the dedicated business event logger
5+
BUSINESS_LOGGER_ENABLED=false
6+
# Path to the SQLite database file for business events
7+
BUSINESS_LOGGER_DB_FILE=./logs/business_events.db
8+
# Table name for business events
9+
BUSINESS_LOGGER_TABLE_NAME=business_events

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
.idea

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tinydb

src/pubsub/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""A simple and robust WebSocket Pub/Sub client for Python.
2+
3+
This package provides a client for connecting to Socket.IO-based
4+
Pub/Sub servers, with automatic reconnection, message queuing,
5+
and topic-based subscription support.
6+
"""
7+
from .json_business_logger import json_business_logger
8+
# ... (autres imports)
9+
from .sqlite_business_logger import sqlite_business_logger
10+
11+
__version__ = "0.1.0"
12+
13+
__all__ = [
14+
"sqlite_business_logger",
15+
"json_business_logger" # <-- NOUVEL EXPORT
16+
]

src/pubsub/json_business_logger.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import datetime
2+
import os
3+
import queue
4+
import threading
5+
from typing import Optional, Dict, Any
6+
7+
from tinydb import TinyDB
8+
9+
10+
class JsonBusinessLogger:
11+
"""
12+
Équivalent du BusinessLogger mais utilisant un fichier JSON comme stockage via TinyDB.
13+
Même fonctionnement : auto-configurable, thread-safe et non-bloquant.
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+
self.db: Optional[TinyDB] = None
34+
35+
def _lazy_initialize(self):
36+
"""Effectue l'initialisation une seule fois à partir des variables d'environnement."""
37+
with self._init_lock:
38+
if self._initialized_flag:
39+
return
40+
41+
enabled = os.getenv("JSON_BUSINESS_LOGGER_ENABLED", "false").lower() in ("true", "1", "yes")
42+
db_file = os.getenv("JSON_BUSINESS_LOGGER_DB_FILE")
43+
44+
if enabled and db_file:
45+
try:
46+
path_dirname = os.path.dirname(db_file)
47+
if len(path_dirname) > 0:
48+
os.makedirs(path_dirname, exist_ok=True)
49+
# os.makedirs(os.path.dirname(db_file), exist_ok=True)
50+
self.db = TinyDB(db_file, indent=2, ensure_ascii=False)
51+
self.is_enabled = True
52+
self.log_queue = queue.Queue()
53+
self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
54+
self.worker_thread.start()
55+
print(f"✅ JsonBusinessLogger auto-configuré. Logs dans '{db_file}'.")
56+
except Exception as e:
57+
print(f"❌ Erreur lors de l'initialisation de JsonBusinessLogger : {e}")
58+
self.is_enabled = False
59+
60+
self._initialized_flag = True
61+
62+
def _process_queue(self):
63+
"""Méthode du thread de travail qui insère les documents dans le fichier JSON."""
64+
while True:
65+
try:
66+
log_item = self.log_queue.get()
67+
if log_item is None:
68+
break
69+
70+
if self.db:
71+
self.db.insert(log_item)
72+
self.log_queue.task_done()
73+
except Exception as e:
74+
print(f"❌ Erreur dans le worker JsonBusinessLogger : {e}")
75+
76+
def log(self, event_type: str, details: Optional[Dict[str, Any]] = None):
77+
"""Enregistre un événement. Imprime en console et met en file pour écriture."""
78+
if not self._initialized_flag:
79+
self._lazy_initialize()
80+
81+
if not self.is_enabled:
82+
return
83+
84+
# Impression console en vert
85+
details_str = f"- {details}" if details else ""
86+
console_output = f"[EVENT-JSON] {event_type} {details_str}"
87+
print(f"{self._GREEN}{console_output}{self._RESET}")
88+
89+
# Création du document et mise en file d'attente
90+
log_document = {
91+
'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(),
92+
'event_type': event_type,
93+
'details': details
94+
}
95+
self.log_queue.put(log_document)
96+
97+
def shutdown(self, wait=True):
98+
"""Arrête proprement le logger."""
99+
if not self._initialized_flag:
100+
self._lazy_initialize()
101+
102+
if not self.is_enabled or not hasattr(self, 'log_queue'):
103+
return
104+
105+
if wait:
106+
self.log_queue.join()
107+
108+
self.log_queue.put(None)
109+
if hasattr(self, 'worker_thread'):
110+
self.worker_thread.join(timeout=5)
111+
112+
if self.db:
113+
self.db.close()
114+
print("✅ JsonBusinessLogger arrêté proprement.")
115+
116+
117+
# Instance singleton qui sera importée dans le reste de l'application.
118+
json_business_logger = JsonBusinessLogger()
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import datetime
2+
import json
3+
import os
4+
import queue
5+
import sqlite3
6+
import threading
7+
from typing import Optional, Dict, Any
8+
9+
10+
class SqliteBusinessLogger:
11+
"""
12+
Un logger métier auto-configurable, thread-safe et non-bloquant.
13+
Il s'initialise automatiquement à partir des variables d'environnement
14+
lors de sa première utilisation.
15+
"""
16+
_instance = None
17+
_lock = threading.Lock()
18+
19+
_GREEN = "\033[92m"
20+
_RESET = "\033[0m"
21+
22+
def __new__(cls, *args, **kwargs):
23+
if not cls._instance:
24+
with cls._lock:
25+
if not cls._instance:
26+
cls._instance = super().__new__(cls)
27+
return cls._instance
28+
29+
def __init__(self):
30+
if not hasattr(self, '_initialized_flag'):
31+
self._initialized_flag = False
32+
self.is_enabled = False
33+
self._init_lock = threading.Lock()
34+
35+
def _lazy_initialize(self):
36+
"""
37+
Effectue l'initialisation une seule fois, de manière thread-safe.
38+
"""
39+
with self._init_lock:
40+
if self._initialized_flag:
41+
return
42+
43+
enabled = os.getenv("BUSINESS_LOGGER_ENABLED", "false").lower() in ("true", "1", "yes")
44+
db_file = os.getenv("BUSINESS_LOGGER_DB_FILE")
45+
46+
if enabled and db_file:
47+
self.is_enabled = True
48+
self.db_file = db_file
49+
self.table_name = os.getenv("BUSINESS_LOGGER_TABLE_NAME", "business_events")
50+
self.log_queue = queue.Queue()
51+
self._create_table()
52+
self.worker_thread = threading.Thread(target=self._process_queue, daemon=True)
53+
self.worker_thread.start()
54+
print(f"✅ BusinessLogger auto-configuré. Logs dans '{self.db_file}'.")
55+
56+
self._initialized_flag = True
57+
58+
def _create_table(self):
59+
"""Crée la table de la base de données si elle n'existe pas."""
60+
try:
61+
path_dirname = os.path.dirname(self.db_file)
62+
if len(path_dirname) > 0:
63+
os.makedirs(path_dirname, exist_ok=True)
64+
with sqlite3.connect(self.db_file) as conn:
65+
cursor = conn.cursor()
66+
cursor.execute(f"""
67+
CREATE TABLE IF NOT EXISTS {self.table_name} (
68+
id INTEGER PRIMARY KEY AUTOINCREMENT,
69+
timestamp TEXT NOT NULL,
70+
event_type TEXT NOT NULL,
71+
details_json TEXT
72+
)
73+
""")
74+
conn.commit()
75+
except Exception as e:
76+
print(f"❌ Erreur lors de la création de la table pour BusinessLogger : {e}")
77+
self.is_enabled = False
78+
79+
def _process_queue(self):
80+
"""Méthode exécutée par le thread de travail pour écrire en BDD."""
81+
with sqlite3.connect(self.db_file, check_same_thread=False) as conn:
82+
cursor = conn.cursor()
83+
while True:
84+
try:
85+
log_item = self.log_queue.get()
86+
if log_item is None:
87+
break
88+
89+
timestamp, event_type, details = log_item
90+
details_json = json.dumps(details) if details else None
91+
92+
cursor.execute(
93+
f"INSERT INTO {self.table_name} (timestamp, event_type, details_json) VALUES (?, ?, ?)",
94+
(timestamp, event_type, details_json)
95+
)
96+
conn.commit()
97+
self.log_queue.task_done()
98+
except Exception as e:
99+
print(f"❌ Erreur dans le worker BusinessLogger : {e}")
100+
101+
def log(self, event_type: str, details: Optional[Dict[str, Any]] = None):
102+
"""
103+
Enregistre un événement métier. S'initialise au premier appel.
104+
Imprime également le log en vert sur la console.
105+
"""
106+
if not self._initialized_flag:
107+
self._lazy_initialize()
108+
109+
if not self.is_enabled:
110+
return
111+
112+
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
113+
114+
details_str = f"- {details}" if details else ""
115+
console_output = f"[EVENT] {event_type} {details_str}"
116+
print(f"{self._GREEN}{console_output}{self._RESET}")
117+
118+
# La logique existante pour la mise en file d'attente reste inchangée
119+
self.log_queue.put((timestamp, event_type, details))
120+
121+
def shutdown(self, wait=True):
122+
"""Arrête proprement le logger."""
123+
if not self._initialized_flag:
124+
self._lazy_initialize()
125+
126+
if not self.is_enabled or not hasattr(self, 'log_queue'):
127+
return
128+
129+
if wait:
130+
self.log_queue.join()
131+
132+
self.log_queue.put(None)
133+
if hasattr(self, 'worker_thread'):
134+
self.worker_thread.join(timeout=5)
135+
print("✅ BusinessLogger arrêté proprement.")
136+
137+
138+
# L'instance singleton est créée, mais pas encore configurée.
139+
sqlite_business_logger = SqliteBusinessLogger()

0 commit comments

Comments
 (0)