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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fsvlog
3
- Version: 0.3.2
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
@@ -1,4 +1,4 @@
1
- __version__ = "0.4.0"
1
+ __version__ = "0.4.2"
2
2
 
3
3
  from typing import Tuple
4
4
  from .fsvlog import FSVLog, Colors, create_logger
@@ -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%S_%f"
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
- result = result[:-3]
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.log_file_path = os.path.join(self.log_folder_path, filename)
148
- self._current_log_file = open(self.log_file_path, "a", encoding="utf-8")
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
- os.rename(self.log_file_path, rotated_name)
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
- try:
168
- os.remove(log_files.pop(0))
169
- except Exception as e:
170
- print(f"Failed to remove old log file: {e}")
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
- while " " in result:
207
- result = result.replace(" ", " ")
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 os.path.getsize(self.log_file_path) > self.max_file_size:
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
- new_logger = FSVLog(
284
- log_folder_path=self.log_folder_path,
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
- return self.bind(type=log_type)
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.log_file_path
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
- self._current_log_file.close()
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.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fsvlog"
7
- version = "0.3.2"
7
+ version = "0.4.2"
8
8
  authors = [
9
9
  {name = "Fsainv028", email = "fsainv028@gmail.com"},
10
10
  ]
File without changes
File without changes
File without changes