fsvlog 0.3.2__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fsvlog-0.3.2 → fsvlog-0.4.2}/PKG-INFO +1 -1
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog/__init__.py +1 -1
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog/fsvlog.py +67 -31
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog.egg-info/PKG-INFO +1 -1
- {fsvlog-0.3.2 → fsvlog-0.4.2}/pyproject.toml +1 -1
- {fsvlog-0.3.2 → fsvlog-0.4.2}/LICENSE.txt +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/README.md +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog.egg-info/SOURCES.txt +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog.egg-info/dependency_links.txt +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog.egg-info/requires.txt +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/fsvlog.egg-info/top_level.txt +0 -0
- {fsvlog-0.3.2 → fsvlog-0.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fsvlog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Advanced logging library with colored output, file rotation, and flexible configuration
|
|
5
5
|
Author-email: Fsainv028 <fsainv028@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Fsainv028/FSVLog
|
|
@@ -37,6 +37,15 @@ LOG_LEVELS = {
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class FSVLog:
|
|
40
|
+
_instances: Dict[str, 'FSVLog'] = {}
|
|
41
|
+
|
|
42
|
+
def __new__(cls, log_folder_path: str = "Logs", **kwargs):
|
|
43
|
+
key = f"{log_folder_path}_{kwargs.get('filename_time_format', '')}_{kwargs.get('file_only', False)}"
|
|
44
|
+
if key not in cls._instances:
|
|
45
|
+
instance = super().__new__(cls)
|
|
46
|
+
cls._instances[key] = instance
|
|
47
|
+
return cls._instances[key]
|
|
48
|
+
|
|
40
49
|
def __init__(self, log_folder_path: str = "Logs", max_logs: int = 10,
|
|
41
50
|
max_file_size_mb: int = 100, log_format: str = None,
|
|
42
51
|
time_format: str = None, filename_time_format: str = None,
|
|
@@ -46,6 +55,8 @@ class FSVLog:
|
|
|
46
55
|
enable_global_exception_logging: bool = True,
|
|
47
56
|
exception_format: str = "compact"):
|
|
48
57
|
|
|
58
|
+
if hasattr(self, '_initialized') and self._initialized:
|
|
59
|
+
return
|
|
49
60
|
if not isinstance(log_folder_path, str) or log_folder_path == "":
|
|
50
61
|
raise ValueError("log_folder_path must be a non-empty string")
|
|
51
62
|
if not isinstance(max_logs, int) or max_logs <= 0:
|
|
@@ -70,7 +81,7 @@ class FSVLog:
|
|
|
70
81
|
self.max_file_size = max_file_size_mb * 1024 * 1024
|
|
71
82
|
self.log_format = log_format or "{timestamp} [{level}] {message}"
|
|
72
83
|
self.time_format = time_format or "%Y-%m-%dT%H:%M:%S.%f"
|
|
73
|
-
self.filename_time_format = filename_time_format or "%Y%m%d_%H%M%
|
|
84
|
+
self.filename_time_format = filename_time_format or "%Y%m%d_%H%M%S"
|
|
74
85
|
self.file_only = file_only
|
|
75
86
|
self.console_only = console_only
|
|
76
87
|
self.use_json = use_json
|
|
@@ -82,6 +93,7 @@ class FSVLog:
|
|
|
82
93
|
|
|
83
94
|
self.colors = Colors()
|
|
84
95
|
self._current_log_file = None
|
|
96
|
+
self._log_file_path = None
|
|
85
97
|
self._bound_context = {}
|
|
86
98
|
self._original_excepthook = None
|
|
87
99
|
|
|
@@ -96,6 +108,11 @@ class FSVLog:
|
|
|
96
108
|
if enable_global_exception_logging:
|
|
97
109
|
self.enable_global_exception_logging()
|
|
98
110
|
|
|
111
|
+
self._initialized = True
|
|
112
|
+
|
|
113
|
+
def __del__(self):
|
|
114
|
+
self.close()
|
|
115
|
+
|
|
99
116
|
def _should_log(self, level: str) -> bool:
|
|
100
117
|
return LOG_LEVELS.get(level, 20) >= self.log_level
|
|
101
118
|
|
|
@@ -118,7 +135,9 @@ class FSVLog:
|
|
|
118
135
|
now = datetime.now()
|
|
119
136
|
result = now.strftime(self.filename_time_format)
|
|
120
137
|
if "%f" in self.filename_time_format:
|
|
121
|
-
|
|
138
|
+
micro = now.microsecond
|
|
139
|
+
micro_str = f"{micro:06d}"[:3]
|
|
140
|
+
result = result.replace(f"{micro:06d}", micro_str)
|
|
122
141
|
return result
|
|
123
142
|
|
|
124
143
|
def _get_caller_info(self) -> Dict[str, Any]:
|
|
@@ -144,16 +163,29 @@ class FSVLog:
|
|
|
144
163
|
|
|
145
164
|
def _open_log_file(self):
|
|
146
165
|
filename = f"log_{self._get_timestamp_for_filename()}.log"
|
|
147
|
-
self.
|
|
148
|
-
self._current_log_file = open(self.
|
|
166
|
+
self._log_file_path = os.path.join(self.log_folder_path, filename)
|
|
167
|
+
self._current_log_file = open(self._log_file_path, "a", encoding="utf-8")
|
|
149
168
|
|
|
150
169
|
def _rotate_log_file(self):
|
|
151
170
|
if self._current_log_file:
|
|
152
171
|
self._current_log_file.close()
|
|
172
|
+
self._current_log_file = None
|
|
153
173
|
|
|
154
174
|
timestamp = self._get_timestamp_for_filename()
|
|
155
175
|
rotated_name = os.path.join(self.log_folder_path, f"log_{timestamp}.log")
|
|
156
|
-
|
|
176
|
+
|
|
177
|
+
if os.path.exists(self._log_file_path):
|
|
178
|
+
try:
|
|
179
|
+
counter = 1
|
|
180
|
+
base_name = rotated_name
|
|
181
|
+
while os.path.exists(rotated_name):
|
|
182
|
+
name, ext = os.path.splitext(base_name)
|
|
183
|
+
rotated_name = f"{name}_{counter}{ext}"
|
|
184
|
+
counter += 1
|
|
185
|
+
os.rename(self._log_file_path, rotated_name)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
print(f"Failed to rotate log: {e}")
|
|
188
|
+
|
|
157
189
|
self._open_log_file()
|
|
158
190
|
|
|
159
191
|
def _manage_log_files(self):
|
|
@@ -163,11 +195,16 @@ class FSVLog:
|
|
|
163
195
|
if os.path.isfile(os.path.join(self.log_folder_path, f)) and f.endswith('.log')
|
|
164
196
|
]
|
|
165
197
|
log_files.sort(key=os.path.getctime)
|
|
198
|
+
|
|
199
|
+
current = self._log_file_path if hasattr(self, '_log_file_path') else None
|
|
200
|
+
|
|
166
201
|
while len(log_files) > self.max_logs:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
file_to_delete = log_files.pop(0)
|
|
203
|
+
if file_to_delete != current:
|
|
204
|
+
try:
|
|
205
|
+
os.remove(file_to_delete)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"Failed to remove old log file: {e}")
|
|
171
208
|
|
|
172
209
|
def _format_log_entry(self, message: str, level: str, extra: Dict[str, Any] = None) -> str:
|
|
173
210
|
timestamp = self._get_timestamp()
|
|
@@ -175,7 +212,9 @@ class FSVLog:
|
|
|
175
212
|
context = {**self._bound_context}
|
|
176
213
|
if extra:
|
|
177
214
|
context.update(extra)
|
|
215
|
+
|
|
178
216
|
log_type = context.pop('type', '')
|
|
217
|
+
|
|
179
218
|
if self.use_json:
|
|
180
219
|
log_data = {
|
|
181
220
|
"timestamp": timestamp,
|
|
@@ -201,10 +240,13 @@ class FSVLog:
|
|
|
201
240
|
}
|
|
202
241
|
try:
|
|
203
242
|
result = self.log_format.format(**format_vars)
|
|
243
|
+
|
|
204
244
|
if not log_type:
|
|
205
245
|
result = result.replace("[{type}]", "").replace("{type}", "").replace(" ", " ").replace("[]", "")
|
|
206
|
-
|
|
207
|
-
|
|
246
|
+
|
|
247
|
+
while " " in result:
|
|
248
|
+
result = result.replace(" ", " ")
|
|
249
|
+
|
|
208
250
|
if not context_str:
|
|
209
251
|
while " " in result:
|
|
210
252
|
result = result.replace(" ", " ")
|
|
@@ -217,7 +259,10 @@ class FSVLog:
|
|
|
217
259
|
if self.console_only:
|
|
218
260
|
return
|
|
219
261
|
try:
|
|
220
|
-
if
|
|
262
|
+
if self._current_log_file is None or self._current_log_file.closed:
|
|
263
|
+
self._open_log_file()
|
|
264
|
+
|
|
265
|
+
if os.path.getsize(self._log_file_path) > self.max_file_size:
|
|
221
266
|
self._rotate_log_file()
|
|
222
267
|
self._current_log_file.write(log_entry + "\n")
|
|
223
268
|
self._current_log_file.flush()
|
|
@@ -280,25 +325,12 @@ class FSVLog:
|
|
|
280
325
|
self.exception_format = format
|
|
281
326
|
|
|
282
327
|
def bind(self, **kwargs) -> 'FSVLog':
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
max_logs=self.max_logs,
|
|
286
|
-
max_file_size_mb=self.max_file_size // (1024 * 1024),
|
|
287
|
-
log_format=self.log_format,
|
|
288
|
-
time_format=self.time_format,
|
|
289
|
-
filename_time_format=self.filename_time_format,
|
|
290
|
-
file_only=self.file_only,
|
|
291
|
-
console_only=self.console_only,
|
|
292
|
-
use_json=self.use_json,
|
|
293
|
-
log_level=self._get_log_level_name(),
|
|
294
|
-
enable_metrics=self.enable_metrics,
|
|
295
|
-
exception_format=self.exception_format
|
|
296
|
-
)
|
|
297
|
-
new_logger._bound_context = {**self._bound_context, **kwargs}
|
|
298
|
-
return new_logger
|
|
328
|
+
self._bound_context.update(kwargs)
|
|
329
|
+
return self
|
|
299
330
|
|
|
300
331
|
def type(self, log_type: str) -> 'FSVLog':
|
|
301
|
-
|
|
332
|
+
self._bound_context['type'] = log_type
|
|
333
|
+
return self
|
|
302
334
|
|
|
303
335
|
def _get_log_level_name(self) -> str:
|
|
304
336
|
for name, value in LOG_LEVELS.items():
|
|
@@ -361,7 +393,7 @@ class FSVLog:
|
|
|
361
393
|
return self._log(message, level.upper(), color, extra)
|
|
362
394
|
|
|
363
395
|
def get_current_log_file(self) -> str:
|
|
364
|
-
return self.
|
|
396
|
+
return self._log_file_path
|
|
365
397
|
|
|
366
398
|
def get_metrics(self) -> Dict[str, int]:
|
|
367
399
|
if not self.enable_metrics:
|
|
@@ -374,7 +406,11 @@ class FSVLog:
|
|
|
374
406
|
|
|
375
407
|
def close(self):
|
|
376
408
|
if hasattr(self, '_current_log_file') and self._current_log_file:
|
|
377
|
-
|
|
409
|
+
try:
|
|
410
|
+
self._current_log_file.close()
|
|
411
|
+
except Exception:
|
|
412
|
+
pass
|
|
413
|
+
self._current_log_file = None
|
|
378
414
|
|
|
379
415
|
|
|
380
416
|
def create_logger(log_folder_path: str = "Logs", max_logs: int = 10, **kwargs):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fsvlog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Advanced logging library with colored output, file rotation, and flexible configuration
|
|
5
5
|
Author-email: Fsainv028 <fsainv028@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Fsainv028/FSVLog
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|