infomankit 0.3.23__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.
- infoman/__init__.py +1 -0
- infoman/cli/README.md +378 -0
- infoman/cli/__init__.py +7 -0
- infoman/cli/commands/__init__.py +3 -0
- infoman/cli/commands/init.py +312 -0
- infoman/cli/scaffold.py +634 -0
- infoman/cli/templates/Makefile.template +132 -0
- infoman/cli/templates/app/__init__.py.template +3 -0
- infoman/cli/templates/app/app.py.template +4 -0
- infoman/cli/templates/app/models_base.py.template +18 -0
- infoman/cli/templates/app/models_entity_init.py.template +11 -0
- infoman/cli/templates/app/models_schemas_init.py.template +11 -0
- infoman/cli/templates/app/repository_init.py.template +11 -0
- infoman/cli/templates/app/routers_init.py.template +15 -0
- infoman/cli/templates/app/services_init.py.template +11 -0
- infoman/cli/templates/app/static_index.html.template +39 -0
- infoman/cli/templates/app/static_main.js.template +31 -0
- infoman/cli/templates/app/static_style.css.template +111 -0
- infoman/cli/templates/app/utils_init.py.template +11 -0
- infoman/cli/templates/config/.env.dev.template +43 -0
- infoman/cli/templates/config/.env.prod.template +43 -0
- infoman/cli/templates/config/README.md.template +28 -0
- infoman/cli/templates/docker/.dockerignore.template +60 -0
- infoman/cli/templates/docker/Dockerfile.template +47 -0
- infoman/cli/templates/docker/README.md.template +240 -0
- infoman/cli/templates/docker/docker-compose.yml.template +81 -0
- infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
- infoman/cli/templates/docker/mysql_init.sql.template +15 -0
- infoman/cli/templates/project/.env.example.template +1 -0
- infoman/cli/templates/project/.gitignore.template +60 -0
- infoman/cli/templates/project/Makefile.template +38 -0
- infoman/cli/templates/project/README.md.template +137 -0
- infoman/cli/templates/project/deploy.sh.template +97 -0
- infoman/cli/templates/project/main.py.template +10 -0
- infoman/cli/templates/project/manage.sh.template +97 -0
- infoman/cli/templates/project/pyproject.toml.template +47 -0
- infoman/cli/templates/project/service.sh.template +203 -0
- infoman/config/__init__.py +25 -0
- infoman/config/base.py +67 -0
- infoman/config/db_cache.py +237 -0
- infoman/config/db_relation.py +181 -0
- infoman/config/db_vector.py +39 -0
- infoman/config/jwt.py +16 -0
- infoman/config/llm.py +16 -0
- infoman/config/log.py +627 -0
- infoman/config/mq.py +26 -0
- infoman/config/settings.py +65 -0
- infoman/llm/__init__.py +0 -0
- infoman/llm/llm.py +297 -0
- infoman/logger/__init__.py +57 -0
- infoman/logger/context.py +191 -0
- infoman/logger/core.py +358 -0
- infoman/logger/filters.py +157 -0
- infoman/logger/formatters.py +138 -0
- infoman/logger/handlers.py +276 -0
- infoman/logger/metrics.py +160 -0
- infoman/performance/README.md +583 -0
- infoman/performance/__init__.py +19 -0
- infoman/performance/cli.py +215 -0
- infoman/performance/config.py +166 -0
- infoman/performance/reporter.py +519 -0
- infoman/performance/runner.py +303 -0
- infoman/performance/standards.py +222 -0
- infoman/service/__init__.py +8 -0
- infoman/service/app.py +67 -0
- infoman/service/core/__init__.py +0 -0
- infoman/service/core/auth.py +105 -0
- infoman/service/core/lifespan.py +132 -0
- infoman/service/core/monitor.py +57 -0
- infoman/service/core/response.py +37 -0
- infoman/service/exception/__init__.py +7 -0
- infoman/service/exception/error.py +274 -0
- infoman/service/exception/exception.py +25 -0
- infoman/service/exception/handler.py +238 -0
- infoman/service/infrastructure/__init__.py +8 -0
- infoman/service/infrastructure/base.py +212 -0
- infoman/service/infrastructure/db_cache/__init__.py +8 -0
- infoman/service/infrastructure/db_cache/manager.py +194 -0
- infoman/service/infrastructure/db_relation/__init__.py +41 -0
- infoman/service/infrastructure/db_relation/manager.py +300 -0
- infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
- infoman/service/infrastructure/db_relation/mysql.py +52 -0
- infoman/service/infrastructure/db_relation/pgsql.py +54 -0
- infoman/service/infrastructure/db_relation/sqllite.py +25 -0
- infoman/service/infrastructure/db_vector/__init__.py +40 -0
- infoman/service/infrastructure/db_vector/manager.py +201 -0
- infoman/service/infrastructure/db_vector/qdrant.py +322 -0
- infoman/service/infrastructure/mq/__init__.py +15 -0
- infoman/service/infrastructure/mq/manager.py +178 -0
- infoman/service/infrastructure/mq/nats/__init__.py +0 -0
- infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
- infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
- infoman/service/launch.py +284 -0
- infoman/service/middleware/__init__.py +7 -0
- infoman/service/middleware/base.py +41 -0
- infoman/service/middleware/logging.py +51 -0
- infoman/service/middleware/rate_limit.py +301 -0
- infoman/service/middleware/request_id.py +21 -0
- infoman/service/middleware/white_list.py +24 -0
- infoman/service/models/__init__.py +8 -0
- infoman/service/models/base.py +441 -0
- infoman/service/models/type/embed.py +70 -0
- infoman/service/routers/__init__.py +18 -0
- infoman/service/routers/health_router.py +311 -0
- infoman/service/routers/monitor_router.py +44 -0
- infoman/service/utils/__init__.py +8 -0
- infoman/service/utils/cache/__init__.py +0 -0
- infoman/service/utils/cache/cache.py +192 -0
- infoman/service/utils/module_loader.py +10 -0
- infoman/service/utils/parse.py +10 -0
- infoman/service/utils/resolver/__init__.py +8 -0
- infoman/service/utils/resolver/base.py +47 -0
- infoman/service/utils/resolver/resp.py +102 -0
- infoman/service/vector/__init__.py +20 -0
- infoman/service/vector/base.py +56 -0
- infoman/service/vector/qdrant.py +125 -0
- infoman/service/vector/service.py +67 -0
- infoman/utils/__init__.py +2 -0
- infoman/utils/decorators/__init__.py +8 -0
- infoman/utils/decorators/cache.py +137 -0
- infoman/utils/decorators/retry.py +99 -0
- infoman/utils/decorators/safe_execute.py +99 -0
- infoman/utils/decorators/timing.py +99 -0
- infoman/utils/encryption/__init__.py +8 -0
- infoman/utils/encryption/aes.py +66 -0
- infoman/utils/encryption/ecc.py +108 -0
- infoman/utils/encryption/rsa.py +112 -0
- infoman/utils/file/__init__.py +0 -0
- infoman/utils/file/handler.py +22 -0
- infoman/utils/hash/__init__.py +0 -0
- infoman/utils/hash/hash.py +61 -0
- infoman/utils/http/__init__.py +8 -0
- infoman/utils/http/client.py +62 -0
- infoman/utils/http/info.py +94 -0
- infoman/utils/http/result.py +19 -0
- infoman/utils/notification/__init__.py +8 -0
- infoman/utils/notification/feishu.py +35 -0
- infoman/utils/text/__init__.py +8 -0
- infoman/utils/text/extractor.py +111 -0
- infomankit-0.3.23.dist-info/METADATA +632 -0
- infomankit-0.3.23.dist-info/RECORD +143 -0
- infomankit-0.3.23.dist-info/WHEEL +4 -0
- infomankit-0.3.23.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/12/23 18:02
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import orjson
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
from threading import Thread, Lock, Event
|
|
14
|
+
from queue import Queue, Empty
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import httpx
|
|
18
|
+
|
|
19
|
+
HTTPX_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HTTPX_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LokiHandler:
|
|
25
|
+
"""
|
|
26
|
+
Loki 日志处理器
|
|
27
|
+
|
|
28
|
+
功能:
|
|
29
|
+
1. 批量推送(减少网络请求)
|
|
30
|
+
2. 异步推送(不阻塞主线程)
|
|
31
|
+
3. 自动重试(网络异常时)
|
|
32
|
+
4. 降级策略(Loki 不可用时写备份文件)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
url: str,
|
|
38
|
+
labels: Dict[str, str],
|
|
39
|
+
batch_size: int = 100,
|
|
40
|
+
flush_interval: float = 5.0,
|
|
41
|
+
timeout: float = 10.0,
|
|
42
|
+
retry_times: int = 3,
|
|
43
|
+
retry_backoff: float = 2.0,
|
|
44
|
+
enable_fallback: bool = True,
|
|
45
|
+
fallback_file: Optional[Path] = None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
初始化 Loki 处理器
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
url: Loki 服务地址
|
|
52
|
+
labels: Loki Labels
|
|
53
|
+
batch_size: 批量推送大小
|
|
54
|
+
flush_interval: 刷新间隔(秒)
|
|
55
|
+
timeout: 推送超时(秒)
|
|
56
|
+
retry_times: 重试次数
|
|
57
|
+
retry_backoff: 重试退避系数
|
|
58
|
+
enable_fallback: 启用降级策略
|
|
59
|
+
fallback_file: 备份文件路径
|
|
60
|
+
"""
|
|
61
|
+
if not HTTPX_AVAILABLE:
|
|
62
|
+
raise ImportError("LokiHandler 需要 httpx: pip install httpx")
|
|
63
|
+
|
|
64
|
+
self.url = f"{url}/loki/api/v1/push"
|
|
65
|
+
self.labels = labels
|
|
66
|
+
self.batch_size = batch_size
|
|
67
|
+
self.flush_interval = flush_interval
|
|
68
|
+
self.timeout = timeout
|
|
69
|
+
self.retry_times = retry_times
|
|
70
|
+
self.retry_backoff = retry_backoff
|
|
71
|
+
self.enable_fallback = enable_fallback
|
|
72
|
+
self.fallback_file = fallback_file
|
|
73
|
+
|
|
74
|
+
# 日志队列
|
|
75
|
+
self.queue: Queue = Queue(maxsize=10000)
|
|
76
|
+
self.batch: List[Dict] = []
|
|
77
|
+
self.lock = Lock()
|
|
78
|
+
|
|
79
|
+
# 后台线程
|
|
80
|
+
self.stop_event = Event()
|
|
81
|
+
self.worker_thread = Thread(target=self._worker, daemon=True)
|
|
82
|
+
self.worker_thread.start()
|
|
83
|
+
|
|
84
|
+
# HTTP 客户端
|
|
85
|
+
self.client = httpx.Client(timeout=timeout)
|
|
86
|
+
|
|
87
|
+
# 统计信息
|
|
88
|
+
self.sent_count = 0
|
|
89
|
+
self.failed_count = 0
|
|
90
|
+
self.fallback_count = 0
|
|
91
|
+
|
|
92
|
+
def write(self, message):
|
|
93
|
+
"""
|
|
94
|
+
写入日志(Loguru 调用)
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
message: 日志消息对象
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
record = message.record
|
|
101
|
+
|
|
102
|
+
# 构造日志条目
|
|
103
|
+
entry = {
|
|
104
|
+
"timestamp": int(record["time"].timestamp() * 1e9), # 纳秒时间戳
|
|
105
|
+
"line": self._format_line(record),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# 放入队列
|
|
109
|
+
self.queue.put_nowait(entry)
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
# 避免日志处理器本身抛出异常
|
|
113
|
+
print(f"LokiHandler 写入失败: {e}")
|
|
114
|
+
|
|
115
|
+
def _format_line(self, record: Dict) -> str:
|
|
116
|
+
"""格式化日志行"""
|
|
117
|
+
return orjson.dumps({
|
|
118
|
+
"level": record["level"].name,
|
|
119
|
+
"logger": record["name"],
|
|
120
|
+
"function": record["function"],
|
|
121
|
+
"line": record["line"],
|
|
122
|
+
"message": record["message"],
|
|
123
|
+
"extra": record.get("extra", {}),
|
|
124
|
+
}).decode('utf-8')
|
|
125
|
+
|
|
126
|
+
def _worker(self):
|
|
127
|
+
"""后台工作线程"""
|
|
128
|
+
last_flush_time = time.time()
|
|
129
|
+
|
|
130
|
+
while not self.stop_event.is_set():
|
|
131
|
+
try:
|
|
132
|
+
# 从队列获取日志
|
|
133
|
+
try:
|
|
134
|
+
entry = self.queue.get(timeout=1.0)
|
|
135
|
+
|
|
136
|
+
with self.lock:
|
|
137
|
+
self.batch.append(entry)
|
|
138
|
+
|
|
139
|
+
except Empty:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
# 检查是否需要刷新
|
|
143
|
+
current_time = time.time()
|
|
144
|
+
should_flush = (
|
|
145
|
+
len(self.batch) >= self.batch_size or
|
|
146
|
+
(self.batch and current_time - last_flush_time >= self.flush_interval)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if should_flush:
|
|
150
|
+
self._flush()
|
|
151
|
+
last_flush_time = current_time
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"LokiHandler 工作线程异常: {e}")
|
|
155
|
+
|
|
156
|
+
# 线程退出前,刷新剩余日志
|
|
157
|
+
self._flush()
|
|
158
|
+
|
|
159
|
+
def _flush(self):
|
|
160
|
+
"""刷新日志批次"""
|
|
161
|
+
with self.lock:
|
|
162
|
+
if not self.batch:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
batch_to_send = self.batch.copy()
|
|
166
|
+
self.batch.clear()
|
|
167
|
+
|
|
168
|
+
# 推送到 Loki
|
|
169
|
+
success = self._push_to_loki(batch_to_send)
|
|
170
|
+
|
|
171
|
+
if success:
|
|
172
|
+
self.sent_count += len(batch_to_send)
|
|
173
|
+
else:
|
|
174
|
+
self.failed_count += len(batch_to_send)
|
|
175
|
+
|
|
176
|
+
# 降级策略:写备份文件
|
|
177
|
+
if self.enable_fallback:
|
|
178
|
+
self._write_fallback(batch_to_send)
|
|
179
|
+
|
|
180
|
+
def _push_to_loki(self, batch: List[Dict]) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
推送日志到 Loki
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
batch: 日志批次
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
True: 成功
|
|
189
|
+
False: 失败
|
|
190
|
+
"""
|
|
191
|
+
# 构造 Loki 请求体
|
|
192
|
+
payload = {
|
|
193
|
+
"streams": [
|
|
194
|
+
{
|
|
195
|
+
"stream": self.labels,
|
|
196
|
+
"values": [
|
|
197
|
+
[str(entry["timestamp"]), entry["line"]]
|
|
198
|
+
for entry in batch
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# 重试推送
|
|
205
|
+
for attempt in range(self.retry_times + 1):
|
|
206
|
+
try:
|
|
207
|
+
response = self.client.post(
|
|
208
|
+
self.url,
|
|
209
|
+
json=payload,
|
|
210
|
+
headers={"Content-Type": "application/json"},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if response.status_code == 204:
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
print(
|
|
217
|
+
f"Loki 推送失败 [尝试 {attempt + 1}/{self.retry_times + 1}]: "
|
|
218
|
+
f"HTTP {response.status_code} - {response.text}"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(
|
|
223
|
+
f"Loki 推送异常 [尝试 {attempt + 1}/{self.retry_times + 1}]: {e}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# 退避重试
|
|
227
|
+
if attempt < self.retry_times:
|
|
228
|
+
time.sleep(self.retry_backoff ** attempt)
|
|
229
|
+
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def _write_fallback(self, batch: List[Dict]):
|
|
233
|
+
"""
|
|
234
|
+
写入备份文件
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
batch: 日志批次
|
|
238
|
+
"""
|
|
239
|
+
if not self.fallback_file:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
with open(self.fallback_file, "a", encoding="utf-8") as f:
|
|
244
|
+
for entry in batch:
|
|
245
|
+
f.write(entry["line"] + "\n")
|
|
246
|
+
|
|
247
|
+
self.fallback_count += len(batch)
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
print(f"写入备份文件失败: {e}")
|
|
251
|
+
|
|
252
|
+
def flush(self):
|
|
253
|
+
"""手动刷新"""
|
|
254
|
+
self._flush()
|
|
255
|
+
|
|
256
|
+
def close(self):
|
|
257
|
+
"""关闭处理器"""
|
|
258
|
+
# 停止工作线程
|
|
259
|
+
self.stop_event.set()
|
|
260
|
+
self.worker_thread.join(timeout=5.0)
|
|
261
|
+
|
|
262
|
+
# 关闭 HTTP 客户端
|
|
263
|
+
self.client.close()
|
|
264
|
+
|
|
265
|
+
print(
|
|
266
|
+
f"LokiHandler 已关闭 "
|
|
267
|
+
f"[发送={self.sent_count}, 失败={self.failed_count}, "
|
|
268
|
+
f"备份={self.fallback_count}]"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def __del__(self):
|
|
272
|
+
"""析构函数"""
|
|
273
|
+
try:
|
|
274
|
+
self.close()
|
|
275
|
+
except:
|
|
276
|
+
pass
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/12/23 17:58
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"""日志指标统计"""
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from collections import defaultdict, deque
|
|
14
|
+
from typing import Dict, Optional
|
|
15
|
+
from threading import Lock
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LogMetrics:
|
|
19
|
+
"""日志指标统计器"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, window_size: int = 60):
|
|
22
|
+
"""
|
|
23
|
+
Args:
|
|
24
|
+
window_size: 统计窗口大小(秒)
|
|
25
|
+
"""
|
|
26
|
+
self.window_size = window_size
|
|
27
|
+
self.lock = Lock()
|
|
28
|
+
|
|
29
|
+
# 日志计数(按级别)
|
|
30
|
+
self.counts: Dict[str, int] = defaultdict(int)
|
|
31
|
+
|
|
32
|
+
# 时间序列(用于速率计算)
|
|
33
|
+
self.timestamps: Dict[str, deque] = defaultdict(
|
|
34
|
+
lambda: deque(maxlen=1000)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# 错误统计
|
|
38
|
+
self.error_count = 0
|
|
39
|
+
self.error_timestamps = deque(maxlen=1000)
|
|
40
|
+
|
|
41
|
+
def record(self, level: str):
|
|
42
|
+
"""
|
|
43
|
+
记录日志
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
level: 日志级别
|
|
47
|
+
"""
|
|
48
|
+
with self.lock:
|
|
49
|
+
current_time = time.time()
|
|
50
|
+
|
|
51
|
+
# 更新计数
|
|
52
|
+
self.counts[level] += 1
|
|
53
|
+
self.timestamps[level].append(current_time)
|
|
54
|
+
|
|
55
|
+
# 记录错误
|
|
56
|
+
if level in ("ERROR", "CRITICAL"):
|
|
57
|
+
self.error_count += 1
|
|
58
|
+
self.error_timestamps.append(current_time)
|
|
59
|
+
|
|
60
|
+
def get_counts(self) -> Dict[str, int]:
|
|
61
|
+
"""获取日志计数"""
|
|
62
|
+
with self.lock:
|
|
63
|
+
return dict(self.counts)
|
|
64
|
+
|
|
65
|
+
def get_rate(self, level: str) -> float:
|
|
66
|
+
"""
|
|
67
|
+
获取日志速率(每秒)
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
level: 日志级别
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
每秒日志数
|
|
74
|
+
"""
|
|
75
|
+
with self.lock:
|
|
76
|
+
timestamps = self.timestamps[level]
|
|
77
|
+
if not timestamps:
|
|
78
|
+
return 0.0
|
|
79
|
+
|
|
80
|
+
current_time = time.time()
|
|
81
|
+
|
|
82
|
+
# 过滤窗口内的时间戳
|
|
83
|
+
recent = [
|
|
84
|
+
ts for ts in timestamps
|
|
85
|
+
if current_time - ts <= self.window_size
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
if not recent:
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
duration = current_time - recent[0]
|
|
92
|
+
return len(recent) / duration if duration > 0 else 0.0
|
|
93
|
+
|
|
94
|
+
def get_error_rate(self) -> float:
|
|
95
|
+
"""获取错误速率(每秒)"""
|
|
96
|
+
with self.lock:
|
|
97
|
+
if not self.error_timestamps:
|
|
98
|
+
return 0.0
|
|
99
|
+
|
|
100
|
+
current_time = time.time()
|
|
101
|
+
|
|
102
|
+
# 过滤窗口内的时间戳
|
|
103
|
+
recent = [
|
|
104
|
+
ts for ts in self.error_timestamps
|
|
105
|
+
if current_time - ts <= self.window_size
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
if not recent:
|
|
109
|
+
return 0.0
|
|
110
|
+
|
|
111
|
+
duration = current_time - recent[0]
|
|
112
|
+
return len(recent) / duration if duration > 0 else 0.0
|
|
113
|
+
|
|
114
|
+
def check_error_threshold(self, threshold: int, window: int) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
检查错误阈值
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
threshold: 错误阈值
|
|
120
|
+
window: 时间窗口(秒)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True: 超过阈值
|
|
124
|
+
False: 未超过阈值
|
|
125
|
+
"""
|
|
126
|
+
with self.lock:
|
|
127
|
+
current_time = time.time()
|
|
128
|
+
|
|
129
|
+
# 统计窗口内的错误数
|
|
130
|
+
recent_errors = sum(
|
|
131
|
+
1 for ts in self.error_timestamps
|
|
132
|
+
if current_time - ts <= window
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return recent_errors >= threshold
|
|
136
|
+
|
|
137
|
+
def get_summary(self) -> Dict[str, any]:
|
|
138
|
+
"""获取统计摘要"""
|
|
139
|
+
with self.lock:
|
|
140
|
+
return {
|
|
141
|
+
"total_counts": dict(self.counts),
|
|
142
|
+
"error_count": self.error_count,
|
|
143
|
+
"error_rate": round(self.get_error_rate(), 2),
|
|
144
|
+
"rates": {
|
|
145
|
+
level: round(self.get_rate(level), 2)
|
|
146
|
+
for level in self.counts.keys()
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# 全局指标实例
|
|
152
|
+
_metrics_instance: Optional[LogMetrics] = None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_metrics() -> LogMetrics:
|
|
156
|
+
"""获取指标实例"""
|
|
157
|
+
global _metrics_instance
|
|
158
|
+
if _metrics_instance is None:
|
|
159
|
+
_metrics_instance = LogMetrics()
|
|
160
|
+
return _metrics_instance
|