mdbq 3.9.6__py3-none-any.whl → 3.9.8__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.
mdbq/__version__.py CHANGED
@@ -1 +1 @@
1
- VERSION = '3.9.6'
1
+ VERSION = '3.9.8'
mdbq/log/mylogger.py CHANGED
@@ -9,6 +9,8 @@ import threading
9
9
  import queue
10
10
  from typing import Optional, Dict, Any, List, Callable, Union
11
11
  import atexit
12
+ import traceback
13
+ import inspect
12
14
 
13
15
  try:
14
16
  import psutil
@@ -65,7 +67,8 @@ class MyLogger:
65
67
  filters: Optional[List[Callable]] = None,
66
68
  enable_metrics: bool = False,
67
69
  metrics_interval: int = 300,
68
- message_limited: int = 1000
70
+ message_limited: int = 1000,
71
+ flush_interval: int = 5
69
72
  ):
70
73
  """
71
74
  初始化日志记录器
@@ -84,15 +87,15 @@ class MyLogger:
84
87
  :param enable_metrics: 是否启用系统指标采集
85
88
  :param metrics_interval: 指标采集间隔(秒)
86
89
  :param message_limited: 简化日志内容,避免过长
90
+ :param flush_interval: 定时刷新日志器间隔(秒)
87
91
  """
88
92
  log_path = os.path.join(os.path.expanduser("~"), 'logfile')
89
- if not os.path.isdir(log_path):
90
- os.makedirs(log_path)
91
-
92
93
  self.name = name
93
94
  self.logging_mode = logging_mode.lower()
94
95
  self.log_level = log_level.upper()
95
96
  self.log_file = os.path.join(log_path, log_file)
97
+ if not os.path.isdir(os.path.dirname(self.log_file)):
98
+ os.makedirs(os.path.dirname(self.log_file))
96
99
  self.log_format = log_format
97
100
  self.max_log_size = max_log_size
98
101
  self.backup_count = backup_count
@@ -104,6 +107,7 @@ class MyLogger:
104
107
  self.enable_metrics = enable_metrics and HAS_PSUTIL
105
108
  self.metrics_interval = metrics_interval
106
109
  self.message_limited = message_limited
110
+ self.flush_interval = flush_interval
107
111
 
108
112
  # 上下文相关
109
113
  self._context = threading.local()
@@ -118,6 +122,11 @@ class MyLogger:
118
122
  self._async_thread = None
119
123
  self._stop_event = threading.Event()
120
124
 
125
+ # 定时刷新相关
126
+ self._flush_thread = None
127
+ self._last_flush_time = 0
128
+ self._start_flush_thread()
129
+
121
130
  # 创建日志记录器
122
131
  self.logger = logging.getLogger(name)
123
132
  self._init_logging()
@@ -213,12 +222,9 @@ class MyLogger:
213
222
  'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
214
223
  'level': record.levelname,
215
224
  'message': record.getMessage(),
216
- # 'module': record.module,
217
- 'function': record.funcName,
218
- # 'line': record.lineno,
219
- # 'thread': record.threadName,
220
- # 'process': record.processName,
221
225
  'name': record.name,
226
+ # 'module': record.module,
227
+ # 'function': record.funcName,
222
228
  }
223
229
 
224
230
  # 添加额外字段
@@ -341,6 +347,45 @@ class MyLogger:
341
347
  )
342
348
  self._async_thread.start()
343
349
 
350
+ def log_error_handler(retry_times=0, fallback_level='error'):
351
+ """
352
+ 日志错误处理装饰器
353
+
354
+ 参数:
355
+ - retry_times: 异常时重试次数
356
+ - fallback_level: 降级日志级别
357
+ """
358
+
359
+ def decorator(log_method):
360
+ def wrapper(self, level: str, message: str, extra: Optional[Dict] = None):
361
+ last_exception = None
362
+ for attempt in range(retry_times + 1):
363
+ try:
364
+ return log_method(self, level, message, extra)
365
+ except Exception as e:
366
+ last_exception = e
367
+ if attempt < retry_times:
368
+ time.sleep(0.1 * (attempt + 1)) # 简单的退避策略
369
+ continue
370
+
371
+ try:
372
+ # 降级处理
373
+ logging.basicConfig()
374
+ fallback_logger = logging.getLogger(f"{getattr(self, 'name', 'mylogger')}_fallback")
375
+ fallback_msg = f"[降级处理] {message}"[:1000]
376
+ getattr(fallback_logger, fallback_level)(
377
+ f"日志记录失败(尝试{attempt + 1}次): {e}\n原始消息: {fallback_msg}"
378
+ )
379
+ except:
380
+ sys.stderr.write(f"严重: 日志系统完全失败 - {last_exception}\n")
381
+
382
+ return None
383
+
384
+ return wrapper
385
+
386
+ return decorator
387
+
388
+ @log_error_handler(retry_times=1, fallback_level='warning')
344
389
  def _sync_log(self, level: str, message: str, extra: Optional[Dict] = None):
345
390
  """同步日志记录"""
346
391
  if not hasattr(self.logger, level.lower()):
@@ -369,7 +414,8 @@ class MyLogger:
369
414
  log_extra['context_data'] = self._context.data.copy()
370
415
 
371
416
  # 添加敏感字段过滤
372
- log_extra['过滤'] = self.sensitive_fields
417
+ if self.sensitive_fields:
418
+ log_extra['过滤'] = self.sensitive_fields
373
419
 
374
420
  # 应用日志采样
375
421
  if self.sample_rate < 1.0 and level.lower() in ('debug', 'info'):
@@ -435,18 +481,6 @@ class MyLogger:
435
481
  if hasattr(self._context, 'data'):
436
482
  self._context.data.clear()
437
483
 
438
- def shutdown(self):
439
- """关闭日志记录器,确保所有日志被刷新"""
440
- if self.enable_async:
441
- self._stop_event.set()
442
- if self._async_thread and self._async_thread.is_alive():
443
- self._async_thread.join(timeout=5)
444
-
445
- # 确保所有handler被刷新
446
- for handler in self.logger.handlers:
447
- handler.flush()
448
- handler.close()
449
-
450
484
  def debug(self, message: str, extra: Optional[Dict] = None):
451
485
  """记录调试信息"""
452
486
  self.log('debug', message, extra)
@@ -471,41 +505,46 @@ class MyLogger:
471
505
  """记录异常信息"""
472
506
  if not extra:
473
507
  extra = {}
474
- # # 获取异常发生的实际位置
508
+
509
+ # # 获取完整的异常堆栈
475
510
  # tb = exc_info.__traceback__
511
+ # while tb.tb_next:
512
+ # tb = tb.tb_next # 获取最内层的堆栈帧
476
513
  #
477
- # if tb:
478
- # extra.update({
479
- # 'module': tb.tb_frame.f_globals.get('__name__', ''),
480
- # 'function': tb.tb_frame.f_code.co_name,
481
- # 'line': tb.tb_lineno,
482
- # 'file': tb.tb_frame.f_code.co_filename
483
- # })
484
- # extra['异常'] = str(exc_info)
485
- # extra['类型'] = exc_info.__class__.__name__
486
- # self.log('error', message, extra)
487
-
488
- # 获取完整的异常堆栈
489
- tb = exc_info.__traceback__
490
- while tb.tb_next:
491
- tb = tb.tb_next # 获取最内层的堆栈帧
492
-
493
- extra.update({
494
- 'module': tb.tb_frame.f_globals.get('__name__', ''),
495
- 'function': tb.tb_frame.f_code.co_name,
496
- 'line': tb.tb_lineno,
497
- 'file': tb.tb_frame.f_code.co_filename,
498
- '异常': str(exc_info),
499
- '类型': exc_info.__class__.__name__,
500
- '堆栈': self._format_traceback(exc_info)
501
- })
514
+ # extra.update({
515
+ # 'module': tb.tb_frame.f_globals.get('__name__', ''),
516
+ # 'function': tb.tb_frame.f_code.co_name,
517
+ # 'line': tb.tb_lineno,
518
+ # 'file': tb.tb_frame.f_code.co_filename,
519
+ # '异常': str(exc_info),
520
+ # '类型': exc_info.__class__.__name__,
521
+ # '堆栈': self._format_traceback(exc_info)
522
+ # })
523
+
524
+ # 使用inspect获取调用栈
525
+ frame = inspect.currentframe()
526
+ try:
527
+ # 向上追溯2层(1层是exception方法本身,2层是实际调用位置)
528
+ caller_frame = frame.f_back.f_back
529
+ extra.update({
530
+ 'module': caller_frame.f_globals.get('__name__', ''),
531
+ 'function': caller_frame.f_code.co_name,
532
+ 'line': caller_frame.f_lineno,
533
+ 'file': caller_frame.f_code.co_filename,
534
+ '异常': str(exc_info),
535
+ '类型': exc_info.__class__.__name__,
536
+ '堆栈': self._format_traceback(exc_info)
537
+ })
538
+ finally:
539
+ del frame # 避免循环引用
502
540
 
503
541
  # 直接使用logger的error方法记录,保留原始调用栈
504
542
  self.log('error', message, extra)
505
543
 
506
544
  def _format_traceback(self, exc_info):
507
545
  """格式化异常堆栈"""
508
- import traceback
546
+ if exc_info is None:
547
+ return ""
509
548
  return ''.join(traceback.format_exception(type(exc_info), exc_info, exc_info.__traceback__))
510
549
 
511
550
  def timeit(self, message: str = "Execution time"):
@@ -528,6 +567,63 @@ class MyLogger:
528
567
  extra={'elapsed_seconds': elapsed})
529
568
  return False
530
569
 
570
+ def _start_flush_thread(self):
571
+ """启动定时刷新线程"""
572
+ self._stop_event.clear()
573
+ self._flush_thread = threading.Thread(
574
+ target=self._flush_worker,
575
+ name=f"{self.name}_flush_thread",
576
+ daemon=True
577
+ )
578
+ self._flush_thread.start()
579
+
580
+ def _flush_worker(self):
581
+ """定时刷新工作线程"""
582
+ while not self._stop_event.is_set():
583
+ try:
584
+ time.sleep(self.flush_interval)
585
+ self._flush_handlers()
586
+ except Exception as e:
587
+ try:
588
+ self.logger.error(f"刷新线程异常: {e}",
589
+ extra={'extra_data': {'flush_error': str(e)}})
590
+ except:
591
+ pass
592
+
593
+ def _flush_handlers(self):
594
+ """刷新所有handler"""
595
+ for handler in self.logger.handlers:
596
+ try:
597
+ handler.flush()
598
+ except Exception as e:
599
+ try:
600
+ self.logger.error(f"刷新handler失败: {e}",
601
+ extra={'extra_data': {'handler_flush_error': str(e)}})
602
+ except:
603
+ pass
604
+
605
+ def shutdown(self):
606
+ """关闭日志记录器,确保所有日志被刷新"""
607
+ if self.enable_async:
608
+ self._stop_event.set()
609
+ # 等待队列清空
610
+ while not self._log_queue.empty():
611
+ time.sleep(0.1)
612
+ if self._async_thread and self._async_thread.is_alive():
613
+ self._async_thread.join(timeout=0.5)
614
+
615
+ # 确保所有handler被刷新
616
+ if self._flush_thread:
617
+ self._flush_handlers()
618
+ if self._flush_thread.is_alive():
619
+ self._flush_thread.join(timeout=0.5)
620
+
621
+ # 关闭所有handler
622
+ for handler in self.logger.handlers:
623
+ try:
624
+ handler.close()
625
+ except:
626
+ pass
531
627
 
532
628
  def main():
533
629
  # 创建日志记录器
@@ -535,11 +631,11 @@ def main():
535
631
  name='my_app',
536
632
  logging_mode='both',
537
633
  log_level='DEBUG',
538
- log_file='/Users/xigua/Downloads/my_app.log',
634
+ log_file='my_app.log',
539
635
  log_format='json',
540
636
  max_log_size=50,
541
637
  backup_count=5,
542
- enable_async=True, # 是否启用异步日志
638
+ enable_async=False, # 是否启用异步日志
543
639
  sample_rate=1, # 采样50%的DEBUG/INFO日志
544
640
  sensitive_fields=[], # 敏感字段列表
545
641
  enable_metrics=False, # 是否启用性能指标
@@ -553,4 +649,3 @@ def main():
553
649
 
554
650
  if __name__ == '__main__':
555
651
  pass
556
- main()