mdbq 3.9.5__py3-none-any.whl → 3.9.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.
- mdbq/__version__.py +1 -1
- mdbq/log/{mlogger.py → mylogger.py} +113 -38
- mdbq/mysql/deduplicator.py +595 -0
- mdbq/mysql/mysql.py +146 -431
- mdbq/mysql/uploader.py +1151 -0
- {mdbq-3.9.5.dist-info → mdbq-3.9.7.dist-info}/METADATA +1 -1
- {mdbq-3.9.5.dist-info → mdbq-3.9.7.dist-info}/RECORD +9 -7
- {mdbq-3.9.5.dist-info → mdbq-3.9.7.dist-info}/WHEEL +0 -0
- {mdbq-3.9.5.dist-info → mdbq-3.9.7.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = '3.9.
|
1
|
+
VERSION = '3.9.7'
|
@@ -3,10 +3,12 @@ import logging.handlers
|
|
3
3
|
import datetime
|
4
4
|
import json
|
5
5
|
import os
|
6
|
+
import sys
|
6
7
|
import time
|
7
8
|
import threading
|
8
9
|
import queue
|
9
10
|
from typing import Optional, Dict, Any, List, Callable, Union
|
11
|
+
import atexit
|
10
12
|
|
11
13
|
try:
|
12
14
|
import psutil
|
@@ -49,7 +51,7 @@ class MyLogger:
|
|
49
51
|
|
50
52
|
def __init__(
|
51
53
|
self,
|
52
|
-
name: str = '
|
54
|
+
name: str = 'mylogger.log',
|
53
55
|
logging_mode: str = 'console', # 'both', 'console', 'file', 'none'
|
54
56
|
log_level: str = 'INFO', # 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
55
57
|
log_file: str = 'm_app.log',
|
@@ -63,7 +65,8 @@ class MyLogger:
|
|
63
65
|
filters: Optional[List[Callable]] = None,
|
64
66
|
enable_metrics: bool = False,
|
65
67
|
metrics_interval: int = 300,
|
66
|
-
message_limited: int = 1000
|
68
|
+
message_limited: int = 1000,
|
69
|
+
flush_interval: int = 5
|
67
70
|
):
|
68
71
|
"""
|
69
72
|
初始化日志记录器
|
@@ -82,11 +85,15 @@ class MyLogger:
|
|
82
85
|
:param enable_metrics: 是否启用系统指标采集
|
83
86
|
:param metrics_interval: 指标采集间隔(秒)
|
84
87
|
:param message_limited: 简化日志内容,避免过长
|
88
|
+
:param flush_interval: 定时刷新日志器间隔(秒)
|
85
89
|
"""
|
90
|
+
log_path = os.path.join(os.path.expanduser("~"), 'logfile')
|
86
91
|
self.name = name
|
87
92
|
self.logging_mode = logging_mode.lower()
|
88
93
|
self.log_level = log_level.upper()
|
89
|
-
self.log_file = log_file
|
94
|
+
self.log_file = os.path.join(log_path, log_file)
|
95
|
+
if not os.path.isdir(os.path.dirname(self.log_file)):
|
96
|
+
os.makedirs(os.path.dirname(self.log_file))
|
90
97
|
self.log_format = log_format
|
91
98
|
self.max_log_size = max_log_size
|
92
99
|
self.backup_count = backup_count
|
@@ -98,6 +105,7 @@ class MyLogger:
|
|
98
105
|
self.enable_metrics = enable_metrics and HAS_PSUTIL
|
99
106
|
self.metrics_interval = metrics_interval
|
100
107
|
self.message_limited = message_limited
|
108
|
+
self.flush_interval = flush_interval
|
101
109
|
|
102
110
|
# 上下文相关
|
103
111
|
self._context = threading.local()
|
@@ -112,6 +120,10 @@ class MyLogger:
|
|
112
120
|
self._async_thread = None
|
113
121
|
self._stop_event = threading.Event()
|
114
122
|
|
123
|
+
# 定时刷新相关
|
124
|
+
self._flush_thread = None
|
125
|
+
self._last_flush_time = 0
|
126
|
+
|
115
127
|
# 创建日志记录器
|
116
128
|
self.logger = logging.getLogger(name)
|
117
129
|
self._init_logging()
|
@@ -119,6 +131,8 @@ class MyLogger:
|
|
119
131
|
if self.enable_async:
|
120
132
|
self._start_async_logging()
|
121
133
|
|
134
|
+
atexit.register(self.shutdown)
|
135
|
+
|
122
136
|
def __enter__(self):
|
123
137
|
"""上下文管理器入口"""
|
124
138
|
return self
|
@@ -205,11 +219,12 @@ class MyLogger:
|
|
205
219
|
'timestamp': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
206
220
|
'level': record.levelname,
|
207
221
|
'message': record.getMessage(),
|
208
|
-
'module': record.module,
|
222
|
+
# 'module': record.module,
|
209
223
|
'function': record.funcName,
|
210
|
-
'line': record.lineno,
|
211
|
-
'thread': record.threadName,
|
212
|
-
'process': record.processName,
|
224
|
+
# 'line': record.lineno,
|
225
|
+
# 'thread': record.threadName,
|
226
|
+
# 'process': record.processName,
|
227
|
+
'name': record.name,
|
213
228
|
}
|
214
229
|
|
215
230
|
# 添加额外字段
|
@@ -252,7 +267,8 @@ class MyLogger:
|
|
252
267
|
filename=self.log_file,
|
253
268
|
maxBytes=self.max_log_size * 1024 * 1024,
|
254
269
|
backupCount=self.backup_count,
|
255
|
-
encoding='utf-8'
|
270
|
+
encoding='utf-8',
|
271
|
+
delay=False
|
256
272
|
)
|
257
273
|
file_handler.setFormatter(formatter)
|
258
274
|
self.logger.addHandler(file_handler)
|
@@ -429,13 +445,23 @@ class MyLogger:
|
|
429
445
|
"""关闭日志记录器,确保所有日志被刷新"""
|
430
446
|
if self.enable_async:
|
431
447
|
self._stop_event.set()
|
448
|
+
# 等待队列清空
|
449
|
+
while not self._log_queue.empty():
|
450
|
+
time.sleep(0.1)
|
432
451
|
if self._async_thread and self._async_thread.is_alive():
|
433
|
-
self._async_thread.join(timeout=
|
452
|
+
self._async_thread.join(timeout=2)
|
453
|
+
if self._flush_thread and self._flush_thread.is_alive():
|
454
|
+
self._flush_thread.join(timeout=2)
|
434
455
|
|
435
456
|
# 确保所有handler被刷新
|
457
|
+
self._flush_handlers()
|
458
|
+
|
459
|
+
# 关闭所有handler
|
436
460
|
for handler in self.logger.handlers:
|
437
|
-
|
438
|
-
|
461
|
+
try:
|
462
|
+
handler.close()
|
463
|
+
except:
|
464
|
+
pass
|
439
465
|
|
440
466
|
def debug(self, message: str, extra: Optional[Dict] = None):
|
441
467
|
"""记录调试信息"""
|
@@ -461,10 +487,43 @@ class MyLogger:
|
|
461
487
|
"""记录异常信息"""
|
462
488
|
if not extra:
|
463
489
|
extra = {}
|
464
|
-
|
465
|
-
|
490
|
+
# # 获取异常发生的实际位置
|
491
|
+
# tb = exc_info.__traceback__
|
492
|
+
#
|
493
|
+
# if tb:
|
494
|
+
# extra.update({
|
495
|
+
# 'module': tb.tb_frame.f_globals.get('__name__', ''),
|
496
|
+
# 'function': tb.tb_frame.f_code.co_name,
|
497
|
+
# 'line': tb.tb_lineno,
|
498
|
+
# 'file': tb.tb_frame.f_code.co_filename
|
499
|
+
# })
|
500
|
+
# extra['异常'] = str(exc_info)
|
501
|
+
# extra['类型'] = exc_info.__class__.__name__
|
502
|
+
# self.log('error', message, extra)
|
503
|
+
|
504
|
+
# 获取完整的异常堆栈
|
505
|
+
tb = exc_info.__traceback__
|
506
|
+
while tb.tb_next:
|
507
|
+
tb = tb.tb_next # 获取最内层的堆栈帧
|
508
|
+
|
509
|
+
extra.update({
|
510
|
+
'module': tb.tb_frame.f_globals.get('__name__', ''),
|
511
|
+
'function': tb.tb_frame.f_code.co_name,
|
512
|
+
'line': tb.tb_lineno,
|
513
|
+
'file': tb.tb_frame.f_code.co_filename,
|
514
|
+
'异常': str(exc_info),
|
515
|
+
'类型': exc_info.__class__.__name__,
|
516
|
+
'堆栈': self._format_traceback(exc_info)
|
517
|
+
})
|
518
|
+
|
519
|
+
# 直接使用logger的error方法记录,保留原始调用栈
|
466
520
|
self.log('error', message, extra)
|
467
521
|
|
522
|
+
def _format_traceback(self, exc_info):
|
523
|
+
"""格式化异常堆栈"""
|
524
|
+
import traceback
|
525
|
+
return ''.join(traceback.format_exception(type(exc_info), exc_info, exc_info.__traceback__))
|
526
|
+
|
468
527
|
def timeit(self, message: str = "Execution time"):
|
469
528
|
"""返回一个计时器上下文管理器"""
|
470
529
|
return self._Timer(self, message)
|
@@ -485,6 +544,40 @@ class MyLogger:
|
|
485
544
|
extra={'elapsed_seconds': elapsed})
|
486
545
|
return False
|
487
546
|
|
547
|
+
def _start_flush_thread(self):
|
548
|
+
"""启动定时刷新线程"""
|
549
|
+
self._stop_event.clear()
|
550
|
+
self._flush_thread = threading.Thread(
|
551
|
+
target=self._flush_worker,
|
552
|
+
name=f"{self.name}_flush_thread",
|
553
|
+
daemon=True
|
554
|
+
)
|
555
|
+
self._flush_thread.start()
|
556
|
+
|
557
|
+
def _flush_worker(self):
|
558
|
+
"""定时刷新工作线程"""
|
559
|
+
while not self._stop_event.is_set():
|
560
|
+
try:
|
561
|
+
time.sleep(self.flush_interval)
|
562
|
+
self._flush_handlers()
|
563
|
+
except Exception as e:
|
564
|
+
try:
|
565
|
+
self.logger.error(f"刷新线程异常: {e}",
|
566
|
+
extra={'extra_data': {'flush_error': str(e)}})
|
567
|
+
except:
|
568
|
+
pass
|
569
|
+
|
570
|
+
def _flush_handlers(self):
|
571
|
+
"""刷新所有handler"""
|
572
|
+
for handler in self.logger.handlers:
|
573
|
+
try:
|
574
|
+
handler.flush()
|
575
|
+
except Exception as e:
|
576
|
+
try:
|
577
|
+
self.logger.error(f"刷新handler失败: {e}",
|
578
|
+
extra={'extra_data': {'handler_flush_error': str(e)}})
|
579
|
+
except:
|
580
|
+
pass
|
488
581
|
|
489
582
|
def main():
|
490
583
|
# 创建日志记录器
|
@@ -493,34 +586,16 @@ def main():
|
|
493
586
|
logging_mode='both',
|
494
587
|
log_level='DEBUG',
|
495
588
|
log_file='/Users/xigua/Downloads/my_app.log',
|
496
|
-
log_format='
|
497
|
-
|
589
|
+
log_format='json',
|
590
|
+
max_log_size=50,
|
591
|
+
backup_count=5,
|
592
|
+
enable_async=True, # 是否启用异步日志
|
498
593
|
sample_rate=1, # 采样50%的DEBUG/INFO日志
|
499
|
-
sensitive_fields=[
|
500
|
-
enable_metrics=
|
594
|
+
sensitive_fields=[], # 敏感字段列表
|
595
|
+
enable_metrics=False, # 是否启用性能指标
|
501
596
|
)
|
502
597
|
|
503
|
-
|
504
|
-
def keyword_filter(level, message, extra):
|
505
|
-
blocked_keywords = ['secret', 'password']
|
506
|
-
return not any(keyword in message.lower() for keyword in blocked_keywords)
|
507
|
-
|
508
|
-
logger.add_filter(keyword_filter)
|
509
|
-
|
510
|
-
# 使用上下文管理器
|
511
|
-
with logger.context(request_id='12345', user='admin'):
|
512
|
-
logger.info("开始处理请求")
|
513
|
-
|
514
|
-
# 使用计时器
|
515
|
-
with logger.timeit("数据库查询"):
|
516
|
-
time.sleep(0.1) # 模拟耗时操作
|
517
|
-
|
518
|
-
logger.debug("调试信息", extra={'data': {'key': 'value'}})
|
519
|
-
|
520
|
-
try:
|
521
|
-
1 / 0
|
522
|
-
except Exception as e:
|
523
|
-
logger.exception("发生错误", e)
|
598
|
+
logger.info('123')
|
524
599
|
|
525
600
|
# 确保所有日志被刷新
|
526
601
|
logger.shutdown()
|