mlog-util 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl

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.

Potentially problematic release.


This version of mlog-util might be problematic. Click here for more details.

@@ -1,3 +1,5 @@
1
1
  from .log_manager import LogManager, get_logger
2
2
  from .handlers import MultiProcessSafeSizeRotatingHandler, MultiProcessSafeTimeRotatingHandler
3
3
 
4
+ __version__ = "0.1.7"
5
+
@@ -5,6 +5,8 @@ import glob
5
5
  import errno
6
6
  import logging
7
7
  import portalocker
8
+ from pathlib import Path
9
+ import re
8
10
  from abc import ABC, abstractmethod
9
11
 
10
12
 
@@ -234,20 +236,34 @@ class MultiProcessSafeSizeRotatingHandler(MultiProcessSafeRotatingHandlerBase):
234
236
 
235
237
  def _do_rollover_impl(self):
236
238
  # 轮转备份文件
239
+ log_path = Path(self.filename)
240
+
241
+ # 1. 轮转已存在的备份文件 (例如 .3 -> .4, .2 -> .3, .1 -> .2)
242
+ # 倒序处理,避免覆盖
237
243
  for i in range(self.backupCount - 1, 0, -1):
238
- sfn = f"{self.filename}.{i}"
239
- dfn = f"{self.filename}.{i+1}"
240
- if os.path.exists(sfn):
241
- if os.path.exists(dfn):
242
- os.remove(dfn)
243
- os.rename(sfn, dfn)
244
- if os.path.exists(self.filename):
245
- dfn = f"{self.filename}.1"
246
- if os.path.exists(dfn):
247
- os.remove(dfn)
248
- os.rename(self.filename, dfn)
249
-
250
- # 重新创建空的日志文件
244
+ sfn = log_path.with_suffix(f'.log.{i}')
245
+ dfn = log_path.with_suffix(f'.log.{i+1}')
246
+
247
+ if sfn.exists():
248
+ try:
249
+ # 直接尝试重命名,如果目标文件已存在会失败
250
+ os.rename(sfn, dfn)
251
+ except FileExistsError:
252
+ # 如果失败,说明目标文件已存在,先删除再重命名
253
+ # 这比 "先检查再操作" 更能避免竞态条件
254
+ dfn.unlink() # pathlib 的删除方法
255
+ os.rename(sfn, dfn)
256
+
257
+ # 2. 将当前日志文件重命名为第一个备份 .1
258
+ if log_path.exists():
259
+ dfn = log_path.with_suffix('.log.1')
260
+ try:
261
+ os.rename(log_path, dfn)
262
+ except FileExistsError:
263
+ dfn.unlink()
264
+ os.rename(log_path, dfn)
265
+
266
+ # 重新创建空的日志文件 占位
251
267
  try:
252
268
  with open(self.filename, 'w', encoding='utf-8') as f:
253
269
  pass
@@ -268,7 +284,6 @@ class MultiProcessSafeTimeRotatingHandler(MultiProcessSafeRotatingHandlerBase):
268
284
  super().__init__(filename, backupCount)
269
285
  self.when = when.upper()
270
286
  self.interval = max(1, int(interval)) # 至少为 1
271
- self.last_rollover = int(time.time())
272
287
 
273
288
  # 支持的单位映射
274
289
  self.when_to_seconds = {
@@ -278,29 +293,90 @@ class MultiProcessSafeTimeRotatingHandler(MultiProcessSafeRotatingHandlerBase):
278
293
  'D': 86400, # 天
279
294
  }
280
295
 
281
- def _should_rollover(self, record) -> bool:
282
- now = int(time.time())
283
-
284
296
  if self.when not in self.when_to_seconds:
285
- return False
297
+ raise ValueError(f"Invalid rollover interval specified: {self.when}")
298
+
299
+ # 在初始化时就计算好下一个轮转时间点
300
+ self.rolloverAt = self._compute_next_rollover_time(int(time.time()))
286
301
 
287
- seconds_per_unit = self.when_to_seconds[self.when]
288
- total_interval_seconds = seconds_per_unit * self.interval
302
+ def _compute_next_rollover_time(self, current_time):
303
+ """
304
+ 计算下一个轮转的时间点(时间戳)。
305
+ 这个方法的核心是使用本地时间来计算,确保轮转发生在正确的本地时间。
306
+ """
307
+ t = time.localtime(current_time)
308
+
309
+ # 根据轮转单位,找到当前周期的起始时间点
310
+ if self.when == 'S':
311
+ current_period_start = time.mktime((t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, t.tm_wday, t.tm_yday, t.tm_isdst))
312
+ elif self.when == 'M':
313
+ current_period_start = time.mktime((t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, 0, t.tm_wday, t.tm_yday, t.tm_isdst))
314
+ elif self.when == 'H':
315
+ current_period_start = time.mktime((t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, 0, 0, t.tm_wday, t.tm_yday, t.tm_isdst))
316
+ else: # 'D' or any other
317
+ current_period_start = time.mktime((t.tm_year, t.tm_mon, t.tm_mday, 0, 0, 0, t.tm_wday, t.tm_yday, t.tm_isdst))
289
318
 
290
- # 计算当前属于第几个周期(从 0 开始)
291
- current_cycle = now // total_interval_seconds
292
- last_cycle = self.last_rollover // total_interval_seconds
319
+ # 下一个轮转时间点 = 当前周期起始 + N个周期
320
+ next_rollover_time = current_period_start + (self.interval * self.when_to_seconds[self.when])
293
321
 
294
- return current_cycle > last_cycle
322
+ # 如果计算出的时间点已经过了(例如,程序刚好在边界点启动),则再推后一个周期
323
+ if next_rollover_time <= current_time:
324
+ next_rollover_time += self.interval * self.when_to_seconds[self.when]
325
+
326
+ return next_rollover_time
327
+
328
+ def _should_rollover(self, record) -> bool:
329
+ """
330
+ 修正3: 判断逻辑改为与下一个轮转时间点比较
331
+ """
332
+ # 获取日志记录产生的时间戳
333
+ record_time = int(record.created)
334
+
335
+ # 如果记录的时间已经超过了我们预定的下一个轮转时间点,则触发轮转
336
+ return record_time >= self.rolloverAt
295
337
 
296
338
  def _do_rollover_impl(self):
297
- # 按日期重命名,如 log.txt -> log.txt.2025-09-16
339
+ # 1. 执行轮转:将当前日志文件重命名为带时间戳的文件
298
340
  date_str = time.strftime(self._get_rollover_format())
299
- dfn = f"{self.filename}.{date_str}"
300
- if os.path.exists(self.filename):
301
- os.rename(self.filename, dfn)
302
- with open(self.filename, 'w'): pass
303
- self.last_rollover = int(time.time())
341
+ log_path = Path(self.filename)
342
+ dfn = log_path.with_name(f"{log_path.name}.{date_str}")
343
+
344
+ if log_path.exists():
345
+ try:
346
+ log_path.rename(dfn)
347
+ except FileExistsError:
348
+ dfn.unlink()
349
+ log_path.rename(dfn)
350
+
351
+ # 2. 重新创建空的日志文件(使用你指定的方式)
352
+ try:
353
+ with open(self.filename, 'w', encoding='utf-8') as f:
354
+ pass
355
+ except Exception as e:
356
+ print(f"Failed to recreate log file {self.filename}: {e}")
357
+
358
+ # 3. 更新下一个轮转的时间点(核心逻辑)
359
+ current_time = int(time.time())
360
+ self.rolloverAt = self._compute_next_rollover_time(current_time)
361
+
362
+ # --- 清理旧备份的逻辑 ---
363
+
364
+ # 4. 查找所有匹配的备份文件
365
+ backup_pattern = log_path.with_name(f"{log_path.name}.*")
366
+ backup_files = glob.glob(str(backup_pattern))
367
+
368
+ # 5. 如果备份文件数量超过限制,则进行清理
369
+ if len(backup_files) > self.backupCount:
370
+ # 6. 按文件名(即时间戳)排序,找出最旧的文件
371
+ backup_files.sort()
372
+
373
+ # 7. 计算需要删除的文件数量并删除
374
+ files_to_delete = backup_files[:-self.backupCount]
375
+ for file_to_delete in files_to_delete:
376
+ try:
377
+ Path(file_to_delete).unlink()
378
+ except OSError as e:
379
+ print(f"Error deleting old log file {file_to_delete}: {e}")
304
380
 
305
381
  def _get_rollover_format(self):
306
382
  """
@@ -19,7 +19,7 @@ class LogManager:
19
19
  log_file: Optional[str] = None,
20
20
  add_console: bool = True,
21
21
  level: int = logging.INFO,
22
- custom_handlers: Optional[List[logging.Handler]] = None,
22
+ custom_handlers: Optional[logging.Handler] = None,
23
23
  ) -> logging.Logger:
24
24
  """
25
25
  获取或创建一个配置好的 logger。
@@ -69,10 +69,9 @@ class LogManager:
69
69
  "%(asctime)s | %(name)s | %(levelname)-8s | %(message)s",
70
70
  datefmt="%Y-%m-%d %H:%M:%S"
71
71
  )
72
- for handler in custom_handlers:
73
- if handler not in logger.handlers:
74
- handler.setFormatter(custom_formatter)
75
- logger.addHandler(handler)
72
+ if custom_handlers not in logger.handlers:
73
+ custom_handlers.setFormatter(custom_formatter)
74
+ logger.addHandler(custom_handlers)
76
75
 
77
76
  return logger
78
77
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlog-util
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1,7 @@
1
+ mlog_util/__init__.py,sha256=jQ2B37jm5GLPH4crurCo32GIcM8iLbJegSRiDOYHTNU,167
2
+ mlog_util/handlers.py,sha256=q5ne-in0jolqufrqerurSri1ynKcwLfUE2alPrYipWo,14303
3
+ mlog_util/log_manager.py,sha256=XhytVOHgL4HmneYdAU7r67avtDtzbirDoHi6BtMEGiw,4046
4
+ mlog_util-0.1.7.dist-info/METADATA,sha256=0kz4Y-igGgO6CoE-rTlNsSMBHngFvJGcztEBtcDSGbo,207
5
+ mlog_util-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ mlog_util-0.1.7.dist-info/top_level.txt,sha256=-liqOVoloTs4GLglhy_pzBBpK-ltsV3IZpg0OQsoD_4,10
7
+ mlog_util-0.1.7.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ mlog_util
@@ -1,7 +0,0 @@
1
- mlog-util/__init__.py,sha256=ZzcwRSCJDIHq_enQIRpvl8VRZz7izkL4VsnupRsR3AY,144
2
- mlog-util/handlers.py,sha256=0EXqAlzxVlcfXEO478EdYJSDdmlZZp5lRame4hEGnmA,10638
3
- mlog-util/log_manager.py,sha256=xD1_RmN3O9u1nxqhMqKxAhY00x0pBj_jpvxgL4cFZBE,4088
4
- mlog_util-0.1.5.dist-info/METADATA,sha256=YC6qnqechzjpzu6G7d44TNNkQ3Z6SAhsGm_88jXaeEc,207
5
- mlog_util-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- mlog_util-0.1.5.dist-info/top_level.txt,sha256=KIIIRag8oNdr6y9VYjd08W6--zyg8WBztbOGFniV_-0,10
7
- mlog_util-0.1.5.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- mlog-util