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 +1 -1
- mdbq/log/mylogger.py +147 -52
- mdbq/mysql/deduplicator.py +601 -0
- mdbq/mysql/mysql.py +146 -431
- mdbq/mysql/uploader.py +1154 -0
- {mdbq-3.9.6.dist-info → mdbq-3.9.8.dist-info}/METADATA +1 -1
- {mdbq-3.9.6.dist-info → mdbq-3.9.8.dist-info}/RECORD +9 -7
- {mdbq-3.9.6.dist-info → mdbq-3.9.8.dist-info}/WHEEL +0 -0
- {mdbq-3.9.6.dist-info → mdbq-3.9.8.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = '3.9.
|
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
|
-
|
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
|
-
#
|
478
|
-
#
|
479
|
-
#
|
480
|
-
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
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
|
-
|
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='
|
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=
|
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()
|