mdbq 4.2.12__py3-none-any.whl → 4.2.14__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 mdbq might be problematic. Click here for more details.
- mdbq/__version__.py +1 -1
- mdbq/route/monitor.py +261 -105
- {mdbq-4.2.12.dist-info → mdbq-4.2.14.dist-info}/METADATA +1 -1
- {mdbq-4.2.12.dist-info → mdbq-4.2.14.dist-info}/RECORD +6 -6
- {mdbq-4.2.12.dist-info → mdbq-4.2.14.dist-info}/WHEEL +0 -0
- {mdbq-4.2.12.dist-info → mdbq-4.2.14.dist-info}/top_level.txt +0 -0
mdbq/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '4.2.
|
|
1
|
+
VERSION = '4.2.14'
|
mdbq/route/monitor.py
CHANGED
|
@@ -15,21 +15,21 @@ import os
|
|
|
15
15
|
import json
|
|
16
16
|
import time
|
|
17
17
|
import uuid
|
|
18
|
+
import threading
|
|
18
19
|
import pymysql
|
|
19
20
|
import functools
|
|
21
|
+
import hashlib
|
|
20
22
|
from datetime import datetime, timedelta
|
|
21
23
|
from typing import Dict, Any, Optional
|
|
22
24
|
from dbutils.pooled_db import PooledDB # type: ignore
|
|
23
25
|
from flask import request, g
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
|
|
27
28
|
class RouteMonitor:
|
|
28
29
|
"""
|
|
29
30
|
路由监控核心类
|
|
30
31
|
|
|
31
32
|
负责 API 请求的监控、日志记录和统计分析。
|
|
32
|
-
采用精简设计,专注于核心监控指标,最大程度降低对业务性能的影响。
|
|
33
33
|
|
|
34
34
|
Attributes:
|
|
35
35
|
database (str): 监控数据库名称,默认为 'api监控系统'
|
|
@@ -41,19 +41,50 @@ class RouteMonitor:
|
|
|
41
41
|
- cleanup_old_data: 清理历史数据
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
def __init__(self, database: str = 'api监控系统', pool = None):
|
|
44
|
+
def __init__(self, database: str = 'api监控系统', pool = None, redis_client = None, enable_async: bool = True):
|
|
45
45
|
"""
|
|
46
46
|
初始化监控系统
|
|
47
47
|
|
|
48
48
|
Args:
|
|
49
49
|
database: 数据库名称,默认为 'api监控系统'
|
|
50
50
|
pool: 数据库连接池对象,如果不传则使用默认配置创建
|
|
51
|
+
redis_client: Redis 客户端,用于异步队列(可选,如果不传则同步写入)
|
|
52
|
+
enable_async: 是否启用异步模式(需要 redis_client),默认 True
|
|
51
53
|
"""
|
|
52
54
|
self.database = database
|
|
53
55
|
self.pool = pool
|
|
54
56
|
if self.pool is None:
|
|
55
57
|
self.init_database_pool()
|
|
56
58
|
self.init_database_tables()
|
|
59
|
+
|
|
60
|
+
# Redis 异步队列配置
|
|
61
|
+
self.redis_client = redis_client
|
|
62
|
+
self.enable_async = enable_async and self.redis_client is not None
|
|
63
|
+
# 队列名ASCII化,避免中文带来的跨系统兼容问题
|
|
64
|
+
try:
|
|
65
|
+
db_hash = hashlib.md5(self.database.encode('utf-8')).hexdigest()[:8]
|
|
66
|
+
self.queue_name = f"api_monitor:{db_hash}:tasks"
|
|
67
|
+
except Exception:
|
|
68
|
+
self.queue_name = "api_monitor:tasks"
|
|
69
|
+
|
|
70
|
+
# 线程锁(用于保护统计数据)
|
|
71
|
+
import threading
|
|
72
|
+
self._stats_lock = threading.Lock()
|
|
73
|
+
|
|
74
|
+
# 统计信息
|
|
75
|
+
self._stats = {
|
|
76
|
+
'total_requests': 0,
|
|
77
|
+
'queued_tasks': 0,
|
|
78
|
+
'sync_writes': 0,
|
|
79
|
+
'queue_failures': 0,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if self.enable_async:
|
|
83
|
+
# 使用 Redis 队列模式(适合 uwsgi 多进程)
|
|
84
|
+
pass # 队列消费者需要单独进程运行
|
|
85
|
+
else:
|
|
86
|
+
# 降级为同步模式
|
|
87
|
+
pass
|
|
57
88
|
|
|
58
89
|
def init_database_pool(self):
|
|
59
90
|
"""
|
|
@@ -100,6 +131,7 @@ class RouteMonitor:
|
|
|
100
131
|
connection.close()
|
|
101
132
|
|
|
102
133
|
except Exception as e:
|
|
134
|
+
# 保持原有行为:抛出异常由上层处理
|
|
103
135
|
raise
|
|
104
136
|
|
|
105
137
|
def ensure_database_context(self, cursor):
|
|
@@ -178,7 +210,7 @@ class RouteMonitor:
|
|
|
178
210
|
`失败次数` INT UNSIGNED DEFAULT 0 COMMENT '失败响应次数(状态码 >= 400)',
|
|
179
211
|
`平均耗时` DECIMAL(10,3) DEFAULT 0 COMMENT '平均响应耗时(毫秒)',
|
|
180
212
|
`最大耗时` DECIMAL(10,3) DEFAULT 0 COMMENT '最大响应耗时(毫秒)',
|
|
181
|
-
`最小耗时` DECIMAL(10,3) DEFAULT
|
|
213
|
+
`最小耗时` DECIMAL(10,3) DEFAULT NULL COMMENT '最小响应耗时(毫秒)',
|
|
182
214
|
`独立ip数` INT UNSIGNED DEFAULT 0 COMMENT '访问的独立 ip 数量',
|
|
183
215
|
`创建时间` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
|
|
184
216
|
`更新时间` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
|
|
@@ -223,9 +255,88 @@ class RouteMonitor:
|
|
|
223
255
|
connection.close()
|
|
224
256
|
|
|
225
257
|
except Exception as e:
|
|
226
|
-
#
|
|
258
|
+
# 保持静默降级行为,不影响主应用启动
|
|
227
259
|
pass
|
|
228
260
|
|
|
261
|
+
# ==================== Redis 队列方法 ====================
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _datetime_converter(obj):
|
|
265
|
+
"""
|
|
266
|
+
JSON 序列化时的 datetime 转换器
|
|
267
|
+
|
|
268
|
+
将 datetime 对象转换为特殊格式的字典,便于反序列化时还原
|
|
269
|
+
"""
|
|
270
|
+
if isinstance(obj, datetime):
|
|
271
|
+
return {'__datetime__': True, 'value': obj.isoformat()}
|
|
272
|
+
return str(obj)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _datetime_decoder(dct):
|
|
276
|
+
"""
|
|
277
|
+
JSON 反序列化时的 datetime 解码器
|
|
278
|
+
|
|
279
|
+
将特殊格式的字典还原为 datetime 对象
|
|
280
|
+
"""
|
|
281
|
+
if isinstance(dct, dict) and '__datetime__' in dct:
|
|
282
|
+
return datetime.fromisoformat(dct['value'])
|
|
283
|
+
return dct
|
|
284
|
+
|
|
285
|
+
def _push_to_queue(self, task_data: Dict[str, Any]) -> bool:
|
|
286
|
+
"""
|
|
287
|
+
将监控任务推入 Redis 队列(非阻塞,极快)
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
task_data: 任务数据字典
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
bool: 是否成功推入队列
|
|
294
|
+
"""
|
|
295
|
+
if not self.enable_async:
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
# 将任务数据序列化为 JSON(使用 datetime 转换器)
|
|
300
|
+
task_json = json.dumps(task_data, default=self._datetime_converter, ensure_ascii=False)
|
|
301
|
+
|
|
302
|
+
# 推入 Redis 列表(LPUSH,从左侧推入)
|
|
303
|
+
self.redis_client.lpush(self.queue_name, task_json)
|
|
304
|
+
|
|
305
|
+
with self._stats_lock:
|
|
306
|
+
self._stats['queued_tasks'] += 1
|
|
307
|
+
return True
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
# 静默处理错误
|
|
311
|
+
with self._stats_lock:
|
|
312
|
+
self._stats['queue_failures'] += 1
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
def get_monitor_stats(self) -> Dict[str, Any]:
|
|
316
|
+
"""
|
|
317
|
+
获取监控系统统计信息
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
dict: 统计信息字典
|
|
321
|
+
"""
|
|
322
|
+
with self._stats_lock:
|
|
323
|
+
stats = self._stats.copy()
|
|
324
|
+
|
|
325
|
+
# 添加队列信息
|
|
326
|
+
if self.enable_async:
|
|
327
|
+
try:
|
|
328
|
+
queue_length = self.redis_client.llen(self.queue_name)
|
|
329
|
+
stats['queue_length'] = queue_length
|
|
330
|
+
stats['mode'] = 'async_redis'
|
|
331
|
+
except:
|
|
332
|
+
stats['queue_length'] = -1
|
|
333
|
+
stats['mode'] = 'async_redis_error'
|
|
334
|
+
else:
|
|
335
|
+
stats['queue_length'] = 0
|
|
336
|
+
stats['mode'] = 'sync'
|
|
337
|
+
|
|
338
|
+
return stats
|
|
339
|
+
|
|
229
340
|
# ==================== 辅助方法 ====================
|
|
230
341
|
|
|
231
342
|
def generate_request_id(self) -> str:
|
|
@@ -345,10 +456,31 @@ class RouteMonitor:
|
|
|
345
456
|
elif hasattr(g, 'user_id'):
|
|
346
457
|
user_id = str(g.user_id)
|
|
347
458
|
|
|
348
|
-
#
|
|
459
|
+
# 收集请求参数(GET 参数 + POST 数据)
|
|
349
460
|
request_params = None
|
|
461
|
+
params_dict = {}
|
|
462
|
+
|
|
463
|
+
# 1. 收集 URL 参数(GET)
|
|
350
464
|
if request.args:
|
|
351
|
-
|
|
465
|
+
params_dict.update(dict(request.args))
|
|
466
|
+
|
|
467
|
+
# 2. 收集 POST/PUT/PATCH 请求体数据
|
|
468
|
+
if request.method in ['POST', 'PUT', 'PATCH']:
|
|
469
|
+
try:
|
|
470
|
+
if request.is_json:
|
|
471
|
+
# JSON 数据
|
|
472
|
+
json_data = request.get_json(silent=True)
|
|
473
|
+
if json_data:
|
|
474
|
+
params_dict.update(json_data)
|
|
475
|
+
elif request.form:
|
|
476
|
+
# 表单数据
|
|
477
|
+
params_dict.update(dict(request.form))
|
|
478
|
+
except Exception:
|
|
479
|
+
pass
|
|
480
|
+
|
|
481
|
+
# 3. 脱敏处理
|
|
482
|
+
if params_dict:
|
|
483
|
+
request_params = self.sanitize_params(params_dict)
|
|
352
484
|
|
|
353
485
|
# 构建请求数据字典
|
|
354
486
|
request_data = {
|
|
@@ -436,9 +568,11 @@ class RouteMonitor:
|
|
|
436
568
|
with connection.cursor() as cursor:
|
|
437
569
|
self.ensure_database_context(cursor)
|
|
438
570
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
571
|
+
# 使用请求时间而不是当前时间,避免统计时间不一致
|
|
572
|
+
request_time = request_data.get('请求时间', datetime.now())
|
|
573
|
+
date = request_time.date()
|
|
574
|
+
hour = request_time.hour
|
|
575
|
+
now = datetime.now() # 用于IP统计的最后访问时间
|
|
442
576
|
|
|
443
577
|
# 判断是否成功(状态码 < 400)
|
|
444
578
|
status_code = request_data.get('响应状态码', 500)
|
|
@@ -455,14 +589,19 @@ class RouteMonitor:
|
|
|
455
589
|
) VALUES (
|
|
456
590
|
%s, %s, %s, %s, 1, %s, %s, %s, %s, %s
|
|
457
591
|
) ON DUPLICATE KEY UPDATE
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
592
|
+
`请求总数` = `请求总数` + 1,
|
|
593
|
+
`成功次数` = `成功次数` + %s,
|
|
594
|
+
`失败次数` = `失败次数` + %s,
|
|
595
|
+
`平均耗时` = (
|
|
596
|
+
(`平均耗时` * `请求总数` + %s) / (`请求总数` + 1)
|
|
597
|
+
),
|
|
598
|
+
`最大耗时` = GREATEST(`最大耗时`, %s),
|
|
599
|
+
`最小耗时` = (
|
|
600
|
+
CASE
|
|
601
|
+
WHEN `最小耗时` IS NULL OR `最小耗时` = 0 THEN %s
|
|
602
|
+
ELSE LEAST(`最小耗时`, %s)
|
|
603
|
+
END
|
|
604
|
+
)
|
|
466
605
|
""", (
|
|
467
606
|
date, hour,
|
|
468
607
|
request_data.get('接口路径', ''),
|
|
@@ -470,9 +609,10 @@ class RouteMonitor:
|
|
|
470
609
|
is_success, is_error,
|
|
471
610
|
response_time, response_time, response_time,
|
|
472
611
|
is_success, is_error,
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
612
|
+
response_time,
|
|
613
|
+
response_time,
|
|
614
|
+
response_time,
|
|
615
|
+
response_time
|
|
476
616
|
))
|
|
477
617
|
|
|
478
618
|
# 2. 更新 IP 统计表
|
|
@@ -486,9 +626,9 @@ class RouteMonitor:
|
|
|
486
626
|
`请求总数` = `请求总数` + 1,
|
|
487
627
|
`成功次数` = `成功次数` + %s,
|
|
488
628
|
`失败次数` = `失败次数` + %s,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
629
|
+
`平均耗时` = (
|
|
630
|
+
(`平均耗时` * `请求总数` + %s) / (`请求总数` + 1)
|
|
631
|
+
),
|
|
492
632
|
`最后访问` = %s
|
|
493
633
|
""", (
|
|
494
634
|
date,
|
|
@@ -547,6 +687,10 @@ class RouteMonitor:
|
|
|
547
687
|
request_data = self.collect_request_data(request)
|
|
548
688
|
request_id = request_data.get('请求id', 'unknown')
|
|
549
689
|
|
|
690
|
+
# 统计总请求数(线程安全)
|
|
691
|
+
with self._stats_lock:
|
|
692
|
+
self._stats['total_requests'] += 1
|
|
693
|
+
|
|
550
694
|
try:
|
|
551
695
|
# 执行原函数
|
|
552
696
|
response = func(*args, **kwargs)
|
|
@@ -562,6 +706,12 @@ class RouteMonitor:
|
|
|
562
706
|
elif isinstance(response, tuple) and len(response) > 1:
|
|
563
707
|
# 处理 (data, status_code) 格式的返回
|
|
564
708
|
response_status = response[1]
|
|
709
|
+
# 健壮化:支持 '200 OK' 等字符串状态
|
|
710
|
+
if isinstance(response_status, str):
|
|
711
|
+
try:
|
|
712
|
+
response_status = int(str(response_status).split()[0])
|
|
713
|
+
except Exception:
|
|
714
|
+
response_status = 200
|
|
565
715
|
|
|
566
716
|
# 更新响应数据
|
|
567
717
|
response_data = {
|
|
@@ -569,12 +719,22 @@ class RouteMonitor:
|
|
|
569
719
|
'响应耗时': process_time,
|
|
570
720
|
}
|
|
571
721
|
|
|
572
|
-
#
|
|
573
|
-
self.save_request_log(request_data, response_data)
|
|
574
|
-
|
|
575
|
-
# 更新统计
|
|
722
|
+
# 合并数据
|
|
576
723
|
request_data.update(response_data)
|
|
577
|
-
|
|
724
|
+
|
|
725
|
+
# 【改进】优先使用 Redis 队列(非阻塞)
|
|
726
|
+
queued = self._push_to_queue({
|
|
727
|
+
'type': 'request_log',
|
|
728
|
+
'data': request_data,
|
|
729
|
+
'timestamp': time.time()
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
# 如果队列失败,降级为同步写入
|
|
733
|
+
if not queued:
|
|
734
|
+
with self._stats_lock:
|
|
735
|
+
self._stats['sync_writes'] += 1
|
|
736
|
+
self.save_request_log(request_data, None)
|
|
737
|
+
self.update_statistics(request_data)
|
|
578
738
|
|
|
579
739
|
return response
|
|
580
740
|
|
|
@@ -590,12 +750,22 @@ class RouteMonitor:
|
|
|
590
750
|
'错误信息': f"{type(e).__name__}: {str(e)}"
|
|
591
751
|
}
|
|
592
752
|
|
|
593
|
-
#
|
|
594
|
-
self.save_request_log(request_data, error_data)
|
|
595
|
-
|
|
596
|
-
# 更新统计
|
|
753
|
+
# 合并数据
|
|
597
754
|
request_data.update(error_data)
|
|
598
|
-
|
|
755
|
+
|
|
756
|
+
# 【改进】优先使用 Redis 队列(非阻塞)
|
|
757
|
+
queued = self._push_to_queue({
|
|
758
|
+
'type': 'request_log',
|
|
759
|
+
'data': request_data,
|
|
760
|
+
'timestamp': time.time()
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
# 如果队列失败,降级为同步写入
|
|
764
|
+
if not queued:
|
|
765
|
+
with self._stats_lock:
|
|
766
|
+
self._stats['sync_writes'] += 1
|
|
767
|
+
self.save_request_log(request_data, None)
|
|
768
|
+
self.update_statistics(request_data)
|
|
599
769
|
|
|
600
770
|
# 重新抛出异常,不影响原有错误处理逻辑
|
|
601
771
|
raise
|
|
@@ -754,84 +924,69 @@ class RouteMonitor:
|
|
|
754
924
|
|
|
755
925
|
except Exception as e:
|
|
756
926
|
return {'错误': str(e)}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
# ==================== 全局实例与导出 ====================
|
|
760
|
-
|
|
761
|
-
# 创建全局监控实例
|
|
762
|
-
route_monitor = RouteMonitor()
|
|
763
|
-
|
|
764
|
-
# 导出核心装饰器(推荐使用此方式)
|
|
765
|
-
api_monitor = route_monitor.api_monitor
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
# ==================== 便捷工具函数 ====================
|
|
769
|
-
|
|
770
|
-
def get_request_id() -> Optional[str]:
|
|
771
|
-
"""
|
|
772
|
-
获取当前请求的唯一 ID
|
|
773
|
-
|
|
774
|
-
可在被 @api_monitor 装饰的函数内调用,用于日志关联和问题追踪。
|
|
775
927
|
|
|
776
|
-
|
|
777
|
-
|
|
928
|
+
def consume_queue_tasks(self, batch_size: int = 100, timeout: float = 1.0):
|
|
929
|
+
"""
|
|
930
|
+
从 Redis 队列中消费任务并写入数据库(用于单独的消费者进程)
|
|
778
931
|
|
|
779
|
-
|
|
780
|
-
```python
|
|
781
|
-
@api_monitor
|
|
782
|
-
def my_api():
|
|
783
|
-
req_id = get_request_id()
|
|
784
|
-
logger.info(f"处理请求: {req_id}")
|
|
785
|
-
return {'status': 'ok'}
|
|
786
|
-
```
|
|
787
|
-
"""
|
|
788
|
-
return getattr(g, 'request_id', None)
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
def get_statistics_summary(days: int = 7) -> Dict[str, Any]:
|
|
792
|
-
"""
|
|
793
|
-
获取统计摘要数据
|
|
794
|
-
|
|
795
|
-
Args:
|
|
796
|
-
days: 统计天数,默认 7 天
|
|
932
|
+
这个方法应该在单独的进程中循环调用
|
|
797
933
|
|
|
798
|
-
|
|
799
|
-
|
|
934
|
+
Args:
|
|
935
|
+
batch_size: 每次处理的最大任务数
|
|
936
|
+
timeout: 从队列获取任务的超时时间(秒)
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
int: 处理的任务数量
|
|
940
|
+
"""
|
|
941
|
+
if not self.enable_async:
|
|
942
|
+
return 0
|
|
800
943
|
|
|
801
|
-
|
|
802
|
-
```python
|
|
803
|
-
# 获取最近 7 天的统计
|
|
804
|
-
stats = get_statistics_summary(7)
|
|
805
|
-
print(stats['总体统计'])
|
|
944
|
+
processed = 0
|
|
806
945
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
946
|
+
try:
|
|
947
|
+
# 批量从队列中取出任务(BRPOP,从右侧阻塞弹出)
|
|
948
|
+
for _ in range(batch_size):
|
|
949
|
+
result = self.redis_client.brpop(self.queue_name, timeout=timeout)
|
|
950
|
+
if not result:
|
|
951
|
+
break # 队列为空
|
|
952
|
+
|
|
953
|
+
_, task_json = result
|
|
954
|
+
|
|
955
|
+
try:
|
|
956
|
+
# (Redis 可能返回 bytes)
|
|
957
|
+
if isinstance(task_json, bytes):
|
|
958
|
+
task_json = task_json.decode('utf-8')
|
|
959
|
+
|
|
960
|
+
# 解析任务数据,还原 datetime 对象(使用 datetime 解码器)
|
|
961
|
+
task = json.loads(task_json, object_hook=self._datetime_decoder)
|
|
962
|
+
task_type = task.get('type')
|
|
963
|
+
task_data = task.get('data', {})
|
|
964
|
+
|
|
965
|
+
# 处理不同类型的任务
|
|
966
|
+
if task_type == 'request_log':
|
|
967
|
+
# 写入日志和统计
|
|
968
|
+
self.save_request_log(task_data, None)
|
|
969
|
+
self.update_statistics(task_data)
|
|
970
|
+
processed += 1
|
|
971
|
+
|
|
972
|
+
except Exception as e:
|
|
973
|
+
# 单个任务失败不影响其他任务
|
|
974
|
+
pass
|
|
975
|
+
|
|
976
|
+
return processed
|
|
977
|
+
|
|
978
|
+
except Exception as e:
|
|
979
|
+
# 静默处理错误
|
|
980
|
+
return processed
|
|
812
981
|
|
|
813
982
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
Args:
|
|
822
|
-
days_to_keep: 保留最近多少天的数据,默认 30 天
|
|
823
|
-
|
|
824
|
-
Returns:
|
|
825
|
-
dict: 清理结果统计
|
|
826
|
-
|
|
827
|
-
示例:
|
|
828
|
-
```python
|
|
829
|
-
# 保留最近 30 天的数据,删除更早的
|
|
830
|
-
result = cleanup_old_data(30)
|
|
831
|
-
print(f"清理完成: {result}")
|
|
832
|
-
```
|
|
833
|
-
"""
|
|
834
|
-
return route_monitor.cleanup_old_data(days_to_keep)
|
|
983
|
+
# # ==================== 全局实例与导出 ====================
|
|
984
|
+
|
|
985
|
+
# # 创建全局监控实例
|
|
986
|
+
# route_monitor = RouteMonitor()
|
|
987
|
+
|
|
988
|
+
# # 导出核心装饰器(推荐使用此方式)
|
|
989
|
+
# api_monitor = route_monitor.api_monitor
|
|
835
990
|
|
|
836
991
|
|
|
837
992
|
# ==================== 模块导出列表 ====================
|
|
@@ -841,4 +996,5 @@ __all__ = [
|
|
|
841
996
|
'get_request_id',
|
|
842
997
|
'get_statistics_summary',
|
|
843
998
|
'cleanup_old_data',
|
|
999
|
+
'get_async_stats',
|
|
844
1000
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
|
|
2
|
-
mdbq/__version__.py,sha256=
|
|
2
|
+
mdbq/__version__.py,sha256=cr0O8LE2OkusCNWIOsW9RXnz5146D47fEK2fLTbqnUU,18
|
|
3
3
|
mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
|
|
4
4
|
mdbq/auth/auth_backend.py,sha256=iLN7AqiSq7fQgFtNtge_TIlVOR1hrCSZXH6oId6uGX4,116924
|
|
5
5
|
mdbq/auth/crypto.py,sha256=M0i4dRljJuE30WH_13ythA2QGKPXZm6TgpnYp6aHOzw,17431
|
|
@@ -30,12 +30,12 @@ mdbq/redis/getredis.py,sha256=vdg7YQEjhoMp5QzxygNGx5DQKRnePrcwPYgUrDypA6g,23672
|
|
|
30
30
|
mdbq/redis/redis_cache.py,sha256=JWarX_l7LvdKyxtUNPANAqd-y20Jg5uqmllCbT-fyv8,45752
|
|
31
31
|
mdbq/route/__init__.py,sha256=BT_dAY7V-U2o72bevq1B9Mq9QA7GodwtkxyLNdGaoE8,22
|
|
32
32
|
mdbq/route/analytics.py,sha256=dngj5hVwKddEUy59nSYbOoJ9C7OVrtCmCkvW6Uj9RYM,28097
|
|
33
|
-
mdbq/route/monitor.py,sha256=
|
|
33
|
+
mdbq/route/monitor.py,sha256=mY55Y2m0c_1fmC2A6GNWvkSbW3OsXPs3jOI-9HHTYMw,40738
|
|
34
34
|
mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
|
|
35
35
|
mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
|
|
36
36
|
mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
|
|
37
37
|
mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
|
|
38
|
-
mdbq-4.2.
|
|
39
|
-
mdbq-4.2.
|
|
40
|
-
mdbq-4.2.
|
|
41
|
-
mdbq-4.2.
|
|
38
|
+
mdbq-4.2.14.dist-info/METADATA,sha256=aNX05FUtA3v3lVAh0N2n7q8Wp-usLo5yXiL4H5qN9Lc,364
|
|
39
|
+
mdbq-4.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
mdbq-4.2.14.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
|
|
41
|
+
mdbq-4.2.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|