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
infoman/logger/core.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/12/23 17:59
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
import socket
|
|
11
|
+
from typing import Optional, cast
|
|
12
|
+
from loguru import logger
|
|
13
|
+
from infoman.config.log import get_log_config, LogConfig
|
|
14
|
+
from .formatters import get_console_format, get_file_format, serialize_json
|
|
15
|
+
from .handlers import LokiHandler
|
|
16
|
+
from .filters import (
|
|
17
|
+
ModuleFilter,
|
|
18
|
+
KeywordFilter,
|
|
19
|
+
LevelFilter,
|
|
20
|
+
RateLimitFilter,
|
|
21
|
+
SanitizeFilter,
|
|
22
|
+
)
|
|
23
|
+
from .context import get_all_context
|
|
24
|
+
from .metrics import get_metrics
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LoggerManager:
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: Optional[LogConfig] = None):
|
|
30
|
+
"""
|
|
31
|
+
初始化日志管理器
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config: 日志配置(None 则自动加载)
|
|
35
|
+
"""
|
|
36
|
+
self.config = config or get_log_config()
|
|
37
|
+
self.is_initialized = False
|
|
38
|
+
self._handler_ids = []
|
|
39
|
+
|
|
40
|
+
# 获取主机名和应用名
|
|
41
|
+
self.hostname = socket.gethostname()
|
|
42
|
+
self.app_name = getattr(self.config, 'APP_NAME', 'infoman')
|
|
43
|
+
|
|
44
|
+
def initialize(self):
|
|
45
|
+
"""初始化日志系统"""
|
|
46
|
+
if self.is_initialized:
|
|
47
|
+
logger.warning("日志系统已初始化,跳过重复初始化")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
logger.remove()
|
|
51
|
+
|
|
52
|
+
# 配置日志处理器
|
|
53
|
+
self._setup_console_handler()
|
|
54
|
+
self._setup_file_handlers()
|
|
55
|
+
self._setup_loki_handler()
|
|
56
|
+
|
|
57
|
+
# 配置全局过滤器
|
|
58
|
+
self._setup_filters()
|
|
59
|
+
|
|
60
|
+
# 配置上下文注入
|
|
61
|
+
self._setup_context_injection()
|
|
62
|
+
|
|
63
|
+
# 配置指标统计
|
|
64
|
+
if self.config.LOG_ENABLE_METRICS:
|
|
65
|
+
self._setup_metrics()
|
|
66
|
+
|
|
67
|
+
self.is_initialized = True
|
|
68
|
+
|
|
69
|
+
logger.info(
|
|
70
|
+
f"日志系统初始化完成 [ENV={self.config.ENV}, "
|
|
71
|
+
f"LEVEL={self.config.LOG_LEVEL}, "
|
|
72
|
+
f"FORMAT={self.config.LOG_FORMAT}]"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _setup_console_handler(self):
|
|
76
|
+
if not self.config.LOG_ENABLE_CONSOLE:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
console_format = get_console_format(self.config.LOG_FORMAT)
|
|
80
|
+
handler_config = {
|
|
81
|
+
"sink": sys.stderr,
|
|
82
|
+
"level": self.config.LOG_LEVEL_CONSOLE or self.config.LOG_LEVEL,
|
|
83
|
+
"format": console_format,
|
|
84
|
+
"colorize": self.config.LOG_ENABLE_CONSOLE_COLOR,
|
|
85
|
+
"backtrace": self.config.LOG_ENABLE_BACKTRACE,
|
|
86
|
+
"diagnose": self.config.LOG_ENABLE_DIAGNOSE,
|
|
87
|
+
}
|
|
88
|
+
handler_id = logger.add(**handler_config)
|
|
89
|
+
self._handler_ids.append(handler_id)
|
|
90
|
+
|
|
91
|
+
logger.debug("控制台处理器已配置")
|
|
92
|
+
|
|
93
|
+
def _setup_file_handlers(self):
|
|
94
|
+
if not self.config.LOG_ENABLE_FILE:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
file_format = get_file_format(self.config.LOG_FORMAT)
|
|
98
|
+
|
|
99
|
+
# 通用文件配置
|
|
100
|
+
common_config = {
|
|
101
|
+
"format": file_format,
|
|
102
|
+
"rotation": self.config.LOG_ROTATION,
|
|
103
|
+
"retention": self.config.LOG_RETENTION,
|
|
104
|
+
"compression": self.config.LOG_COMPRESSION,
|
|
105
|
+
"encoding": self.config.LOG_FILE_ENCODING,
|
|
106
|
+
"backtrace": self.config.LOG_ENABLE_BACKTRACE,
|
|
107
|
+
"diagnose": self.config.LOG_ENABLE_DIAGNOSE,
|
|
108
|
+
"enqueue": self.config.LOG_ENABLE_ASYNC,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# JSON 格式特殊处理
|
|
112
|
+
if self.config.LOG_FORMAT == "json":
|
|
113
|
+
common_config["serialize"] = True
|
|
114
|
+
|
|
115
|
+
# ========== all.log(所有日志) ==========
|
|
116
|
+
if self.config.LOG_ENABLE_ALL_FILE:
|
|
117
|
+
handler_id = logger.add(
|
|
118
|
+
sink=self.config.LOG_DIR / "all.log",
|
|
119
|
+
level=cast(str, self.config.LOG_LEVEL_FILE or self.config.LOG_LEVEL),
|
|
120
|
+
**common_config
|
|
121
|
+
)
|
|
122
|
+
self._handler_ids.append(handler_id)
|
|
123
|
+
logger.debug("all.log 处理器已配置")
|
|
124
|
+
|
|
125
|
+
# ========== 按级别分割日志 ==========
|
|
126
|
+
if self.config.LOG_ENABLE_SPLIT_BY_LEVEL:
|
|
127
|
+
# info.log(INFO 及以上)
|
|
128
|
+
handler_id = logger.add(
|
|
129
|
+
sink=self.config.LOG_DIR / "info.log",
|
|
130
|
+
level="INFO",
|
|
131
|
+
filter=lambda record: record["level"].name == "INFO",
|
|
132
|
+
**common_config
|
|
133
|
+
)
|
|
134
|
+
self._handler_ids.append(handler_id)
|
|
135
|
+
|
|
136
|
+
# warning.log(WARNING 及以上)
|
|
137
|
+
handler_id = logger.add(
|
|
138
|
+
sink=self.config.LOG_DIR / "warning.log",
|
|
139
|
+
level="WARNING",
|
|
140
|
+
filter=lambda record: record["level"].name == "WARNING",
|
|
141
|
+
**common_config
|
|
142
|
+
)
|
|
143
|
+
self._handler_ids.append(handler_id)
|
|
144
|
+
|
|
145
|
+
# error.log(ERROR 及以上)
|
|
146
|
+
handler_id = logger.add(
|
|
147
|
+
sink=self.config.LOG_DIR / "error.log",
|
|
148
|
+
level="ERROR",
|
|
149
|
+
filter=lambda record: record["level"].name in ("ERROR", "CRITICAL"),
|
|
150
|
+
**common_config
|
|
151
|
+
)
|
|
152
|
+
self._handler_ids.append(handler_id)
|
|
153
|
+
|
|
154
|
+
logger.debug("分级日志文件处理器已配置")
|
|
155
|
+
|
|
156
|
+
# ========== debug.log(开发环境) ==========
|
|
157
|
+
if self.config.LOG_ENABLE_DEBUG_FILE:
|
|
158
|
+
handler_id = logger.add(
|
|
159
|
+
sink=self.config.LOG_DIR / "debug.log",
|
|
160
|
+
level="DEBUG",
|
|
161
|
+
**common_config
|
|
162
|
+
)
|
|
163
|
+
self._handler_ids.append(handler_id)
|
|
164
|
+
logger.debug("debug.log 处理器已配置")
|
|
165
|
+
|
|
166
|
+
def _setup_loki_handler(self):
|
|
167
|
+
"""配置 Loki 处理器"""
|
|
168
|
+
if not self.config.LOG_ENABLE_LOKI:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
loki_handler = LokiHandler(
|
|
173
|
+
url=self.config.LOG_LOKI_URL,
|
|
174
|
+
labels=self.config.get_loki_labels(),
|
|
175
|
+
batch_size=self.config.LOG_LOKI_BATCH_SIZE,
|
|
176
|
+
flush_interval=self.config.LOG_LOKI_FLUSH_INTERVAL,
|
|
177
|
+
timeout=self.config.LOG_LOKI_TIMEOUT,
|
|
178
|
+
retry_times=self.config.LOG_LOKI_RETRY_TIMES,
|
|
179
|
+
retry_backoff=self.config.LOG_LOKI_RETRY_BACKOFF,
|
|
180
|
+
enable_fallback=self.config.LOG_LOKI_ENABLE_FALLBACK,
|
|
181
|
+
fallback_file=self.config.LOG_DIR / self.config.LOG_LOKI_FALLBACK_FILE,
|
|
182
|
+
)
|
|
183
|
+
handler_id = logger.add(
|
|
184
|
+
sink=loki_handler,
|
|
185
|
+
level=cast(str, self.config.LOG_LEVEL_LOKI or self.config.LOG_LEVEL),
|
|
186
|
+
format="{message}", # Loki 处理器自己格式化
|
|
187
|
+
enqueue=True, # 异步推送
|
|
188
|
+
)
|
|
189
|
+
self._handler_ids.append(handler_id)
|
|
190
|
+
|
|
191
|
+
logger.info(f"Loki 处理器已配置 [URL={self.config.LOG_LOKI_URL}]")
|
|
192
|
+
|
|
193
|
+
except ImportError:
|
|
194
|
+
logger.error("无法导入 LokiHandler,请检查 httpx 是否安装")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"配置 Loki 处理器失败: {e}")
|
|
197
|
+
|
|
198
|
+
def _setup_filters(self):
|
|
199
|
+
"""配置全局过滤器"""
|
|
200
|
+
# 模块过滤
|
|
201
|
+
if self.config.LOG_FILTER_MODULES:
|
|
202
|
+
module_filter = ModuleFilter(self.config.LOG_FILTER_MODULES)
|
|
203
|
+
logger.add(
|
|
204
|
+
lambda msg: None, # 空处理器
|
|
205
|
+
filter=module_filter,
|
|
206
|
+
)
|
|
207
|
+
logger.debug(f"模块过滤器已配置: {self.config.LOG_FILTER_MODULES}")
|
|
208
|
+
|
|
209
|
+
# 关键词过滤
|
|
210
|
+
if self.config.LOG_FILTER_KEYWORDS:
|
|
211
|
+
keyword_filter = KeywordFilter(self.config.LOG_FILTER_KEYWORDS)
|
|
212
|
+
logger.add(
|
|
213
|
+
lambda msg: None,
|
|
214
|
+
filter=keyword_filter,
|
|
215
|
+
)
|
|
216
|
+
logger.debug(f"关键词过滤器已配置: {self.config.LOG_FILTER_KEYWORDS}")
|
|
217
|
+
|
|
218
|
+
# 速率限制
|
|
219
|
+
if self.config.LOG_ENABLE_RATE_LIMIT:
|
|
220
|
+
rate_limit_filter = RateLimitFilter(
|
|
221
|
+
self.config.LOG_RATE_LIMIT_MESSAGES
|
|
222
|
+
)
|
|
223
|
+
logger.add(
|
|
224
|
+
lambda msg: None,
|
|
225
|
+
filter=rate_limit_filter,
|
|
226
|
+
)
|
|
227
|
+
logger.debug(
|
|
228
|
+
f"速率限制过滤器已配置: "
|
|
229
|
+
f"{self.config.LOG_RATE_LIMIT_MESSAGES} msg/s"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# 敏感信息脱敏
|
|
233
|
+
if self.config.LOG_ENABLE_SANITIZE:
|
|
234
|
+
sanitize_filter = SanitizeFilter(
|
|
235
|
+
self.config.LOG_SANITIZE_FIELDS,
|
|
236
|
+
self.config.LOG_SANITIZE_PATTERN,
|
|
237
|
+
)
|
|
238
|
+
logger.add(
|
|
239
|
+
lambda msg: None,
|
|
240
|
+
filter=sanitize_filter,
|
|
241
|
+
)
|
|
242
|
+
logger.debug(
|
|
243
|
+
f"脱敏过滤器已配置: {len(self.config.LOG_SANITIZE_FIELDS)} 个字段"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def _setup_context_injection(self):
|
|
247
|
+
"""配置上下文注入"""
|
|
248
|
+
if not self.config.LOG_ENABLE_CONTEXT:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# 配置上下文处理器
|
|
252
|
+
def context_patcher(record):
|
|
253
|
+
"""注入上下文信息"""
|
|
254
|
+
context = get_all_context()
|
|
255
|
+
|
|
256
|
+
# 注入到 extra 字段
|
|
257
|
+
record["extra"].update(context)
|
|
258
|
+
|
|
259
|
+
# 注入环境信息
|
|
260
|
+
record["extra"]["hostname"] = self.hostname
|
|
261
|
+
record["extra"]["app_name"] = self.app_name
|
|
262
|
+
record["extra"]["env"] = self.config.ENV
|
|
263
|
+
|
|
264
|
+
logger.configure(patcher=context_patcher)
|
|
265
|
+
logger.debug("上下文注入已配置")
|
|
266
|
+
|
|
267
|
+
def _setup_metrics(self):
|
|
268
|
+
"""配置指标统计"""
|
|
269
|
+
metrics = get_metrics()
|
|
270
|
+
|
|
271
|
+
def metrics_sink(message):
|
|
272
|
+
"""指标统计处理器"""
|
|
273
|
+
record = message.record
|
|
274
|
+
level = record["level"].name
|
|
275
|
+
metrics.record(level)
|
|
276
|
+
|
|
277
|
+
# 检查错误阈值
|
|
278
|
+
if metrics.check_error_threshold(
|
|
279
|
+
self.config.LOG_ERROR_THRESHOLD,
|
|
280
|
+
self.config.LOG_ERROR_WINDOW
|
|
281
|
+
):
|
|
282
|
+
logger.warning(
|
|
283
|
+
f"错误日志超过阈值: "
|
|
284
|
+
f"{self.config.LOG_ERROR_THRESHOLD} 条/"
|
|
285
|
+
f"{self.config.LOG_ERROR_WINDOW} 秒"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
handler_id = logger.add(
|
|
289
|
+
sink=metrics_sink,
|
|
290
|
+
level="DEBUG",
|
|
291
|
+
format="{message}",
|
|
292
|
+
)
|
|
293
|
+
self._handler_ids.append(handler_id)
|
|
294
|
+
|
|
295
|
+
logger.debug("指标统计已配置")
|
|
296
|
+
|
|
297
|
+
def shutdown(self):
|
|
298
|
+
"""关闭日志系统"""
|
|
299
|
+
if not self.is_initialized:
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
logger.info("正在关闭日志系统...")
|
|
303
|
+
|
|
304
|
+
# 移除所有处理器
|
|
305
|
+
for handler_id in self._handler_ids:
|
|
306
|
+
try:
|
|
307
|
+
logger.remove(handler_id)
|
|
308
|
+
except ValueError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
self._handler_ids.clear()
|
|
312
|
+
self.is_initialized = False
|
|
313
|
+
|
|
314
|
+
logger.info("日志系统已关闭")
|
|
315
|
+
|
|
316
|
+
def reload(self):
|
|
317
|
+
"""重新加载日志配置"""
|
|
318
|
+
logger.info("正在重新加载日志配置...")
|
|
319
|
+
|
|
320
|
+
self.shutdown()
|
|
321
|
+
|
|
322
|
+
from infoman.config.log import reload_log_config
|
|
323
|
+
self.config = reload_log_config()
|
|
324
|
+
|
|
325
|
+
self.initialize()
|
|
326
|
+
|
|
327
|
+
logger.info("日志配置已重新加载")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# =================================================================
|
|
331
|
+
# 全局实例
|
|
332
|
+
# =================================================================
|
|
333
|
+
|
|
334
|
+
_logger_manager: Optional[LoggerManager] = None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def setup_logger(config: Optional[LogConfig] = None) -> LoggerManager:
|
|
338
|
+
global _logger_manager
|
|
339
|
+
|
|
340
|
+
if _logger_manager is None:
|
|
341
|
+
_logger_manager = LoggerManager(config)
|
|
342
|
+
_logger_manager.initialize()
|
|
343
|
+
|
|
344
|
+
return _logger_manager
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_logger_manager() -> Optional[LoggerManager]:
|
|
348
|
+
"""获取日志管理器实例"""
|
|
349
|
+
return _logger_manager
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def shutdown_logger():
|
|
353
|
+
"""关闭日志系统"""
|
|
354
|
+
global _logger_manager
|
|
355
|
+
|
|
356
|
+
if _logger_manager is not None:
|
|
357
|
+
_logger_manager.shutdown()
|
|
358
|
+
_logger_manager = None
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/12/23 17:57
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
from typing import List, Set
|
|
11
|
+
from loguru import logger
|
|
12
|
+
import time
|
|
13
|
+
from collections import deque
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ModuleFilter:
|
|
17
|
+
"""模块过滤器"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, excluded_modules: List[str]):
|
|
20
|
+
self.excluded_modules = set(excluded_modules)
|
|
21
|
+
|
|
22
|
+
def __call__(self, record) -> bool:
|
|
23
|
+
"""
|
|
24
|
+
过滤指定模块的日志
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
True: 保留日志
|
|
28
|
+
False: 过滤日志
|
|
29
|
+
"""
|
|
30
|
+
logger_name = record["name"]
|
|
31
|
+
|
|
32
|
+
# 精确匹配
|
|
33
|
+
if logger_name in self.excluded_modules:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
# 前缀匹配
|
|
37
|
+
for module in self.excluded_modules:
|
|
38
|
+
if logger_name.startswith(f"{module}."):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class KeywordFilter:
|
|
45
|
+
|
|
46
|
+
def __init__(self, excluded_keywords: List[str]):
|
|
47
|
+
self.excluded_keywords = [kw.lower() for kw in excluded_keywords]
|
|
48
|
+
|
|
49
|
+
def __call__(self, record) -> bool:
|
|
50
|
+
message = record["message"].lower()
|
|
51
|
+
|
|
52
|
+
for keyword in self.excluded_keywords:
|
|
53
|
+
if keyword in message:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LevelFilter:
|
|
60
|
+
|
|
61
|
+
def __init__(self, min_level: str):
|
|
62
|
+
self.min_level = min_level
|
|
63
|
+
|
|
64
|
+
def __call__(self, record) -> bool:
|
|
65
|
+
return record["level"].name >= self.min_level
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RateLimitFilter:
|
|
69
|
+
|
|
70
|
+
def __init__(self, max_messages_per_second: int):
|
|
71
|
+
self.max_messages = max_messages_per_second
|
|
72
|
+
self.timestamps = deque(maxlen=max_messages_per_second)
|
|
73
|
+
self.dropped_count = 0
|
|
74
|
+
|
|
75
|
+
def __call__(self, record) -> bool:
|
|
76
|
+
current_time = time.time()
|
|
77
|
+
while self.timestamps and current_time - self.timestamps[0] > 1.0:
|
|
78
|
+
self.timestamps.popleft()
|
|
79
|
+
|
|
80
|
+
# 检查是否超过限制
|
|
81
|
+
if len(self.timestamps) >= self.max_messages:
|
|
82
|
+
self.dropped_count += 1
|
|
83
|
+
|
|
84
|
+
# 每丢弃 100 条日志,记录一次警告
|
|
85
|
+
if self.dropped_count % 100 == 0:
|
|
86
|
+
logger.warning(
|
|
87
|
+
f"日志速率限制触发,已丢弃 {self.dropped_count} 条日志"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
self.timestamps.append(current_time)
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SanitizeFilter:
|
|
97
|
+
"""敏感信息脱敏过滤器"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, sensitive_fields: List[str], pattern: str = "***REDACTED***"):
|
|
100
|
+
"""
|
|
101
|
+
Args:
|
|
102
|
+
sensitive_fields: 敏感字段列表
|
|
103
|
+
pattern: 替换模式
|
|
104
|
+
"""
|
|
105
|
+
self.sensitive_fields = [field.lower() for field in sensitive_fields]
|
|
106
|
+
self.pattern = pattern
|
|
107
|
+
|
|
108
|
+
def __call__(self, record) -> bool:
|
|
109
|
+
"""
|
|
110
|
+
脱敏处理
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
True: 始终返回 True(不过滤,只修改)
|
|
114
|
+
"""
|
|
115
|
+
# 脱敏消息
|
|
116
|
+
record["message"] = self._sanitize_text(record["message"])
|
|
117
|
+
|
|
118
|
+
# 脱敏 extra 字段
|
|
119
|
+
if record.get("extra"):
|
|
120
|
+
record["extra"] = self._sanitize_dict(record["extra"])
|
|
121
|
+
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
def _sanitize_text(self, text: str) -> str:
|
|
125
|
+
"""脱敏文本"""
|
|
126
|
+
for field in self.sensitive_fields:
|
|
127
|
+
# 匹配 key=value 或 "key":"value" 格式
|
|
128
|
+
patterns = [
|
|
129
|
+
rf'{field}["\']?\s*[:=]\s*["\']?([^"\',\s}}]+)',
|
|
130
|
+
rf'"{field}"\s*:\s*"([^"]+)"',
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
for pattern in patterns:
|
|
134
|
+
text = re.sub(
|
|
135
|
+
pattern,
|
|
136
|
+
lambda m: m.group(0).replace(m.group(1), self.pattern),
|
|
137
|
+
text,
|
|
138
|
+
flags=re.IGNORECASE
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return text
|
|
142
|
+
|
|
143
|
+
def _sanitize_dict(self, data: dict) -> dict:
|
|
144
|
+
"""脱敏字典"""
|
|
145
|
+
sanitized = {}
|
|
146
|
+
|
|
147
|
+
for key, value in data.items():
|
|
148
|
+
if key.lower() in self.sensitive_fields:
|
|
149
|
+
sanitized[key] = self.pattern
|
|
150
|
+
elif isinstance(value, dict):
|
|
151
|
+
sanitized[key] = self._sanitize_dict(value)
|
|
152
|
+
elif isinstance(value, str):
|
|
153
|
+
sanitized[key] = self._sanitize_text(value)
|
|
154
|
+
else:
|
|
155
|
+
sanitized[key] = value
|
|
156
|
+
|
|
157
|
+
return sanitized
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
日志格式化器
|
|
6
|
+
|
|
7
|
+
使用 orjson 提升 JSON 序列化性能
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import orjson
|
|
11
|
+
import traceback
|
|
12
|
+
from typing import Dict, Any
|
|
13
|
+
|
|
14
|
+
# =================================================================
|
|
15
|
+
# 控制台格式(带颜色)
|
|
16
|
+
# =================================================================
|
|
17
|
+
|
|
18
|
+
CONSOLE_FORMATS = {
|
|
19
|
+
"simple": (
|
|
20
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
21
|
+
"<level>{level: <8}</level> | "
|
|
22
|
+
"<level>{message}</level>"
|
|
23
|
+
),
|
|
24
|
+
|
|
25
|
+
"detailed": (
|
|
26
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
27
|
+
"<level>{level: <8}</level> | "
|
|
28
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
|
29
|
+
"<level>{message}</level>"
|
|
30
|
+
),
|
|
31
|
+
|
|
32
|
+
"debug": (
|
|
33
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
34
|
+
"<magenta>{process}</magenta>:<magenta>{thread}</magenta> | "
|
|
35
|
+
"<level>{level: <8}</level> | "
|
|
36
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
|
37
|
+
"<level>{message}</level>"
|
|
38
|
+
),
|
|
39
|
+
|
|
40
|
+
"json": "{message}", # JSON 格式由 serialize 参数处理
|
|
41
|
+
|
|
42
|
+
"pro": (
|
|
43
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
44
|
+
"{extra[hostname]} | "
|
|
45
|
+
"{extra[app_name]} | "
|
|
46
|
+
"<level>{level: <8}</level> | "
|
|
47
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan> | "
|
|
48
|
+
"<level>{message}</level>"
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# =================================================================
|
|
53
|
+
# 文件格式(无颜色)
|
|
54
|
+
# =================================================================
|
|
55
|
+
|
|
56
|
+
FILE_FORMATS = {
|
|
57
|
+
"simple": (
|
|
58
|
+
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
|
|
59
|
+
"{level: <8} | "
|
|
60
|
+
"{message}"
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
"detailed": (
|
|
64
|
+
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
|
|
65
|
+
"{level: <8} | "
|
|
66
|
+
"{name}:{function}:{line} | "
|
|
67
|
+
"{message}"
|
|
68
|
+
),
|
|
69
|
+
|
|
70
|
+
"debug": (
|
|
71
|
+
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
|
|
72
|
+
"{process}:{thread} | "
|
|
73
|
+
"{level: <8} | "
|
|
74
|
+
"{name}:{function}:{line} | "
|
|
75
|
+
"{message}"
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
"json": "{message}",
|
|
79
|
+
|
|
80
|
+
"pro": (
|
|
81
|
+
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
|
|
82
|
+
"{extra[hostname]} | "
|
|
83
|
+
"{extra[app_name]} | "
|
|
84
|
+
"{level: <8} | "
|
|
85
|
+
"{name}:{function} | "
|
|
86
|
+
"{message}"
|
|
87
|
+
),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_console_format(format_type: str) -> str:
|
|
92
|
+
return CONSOLE_FORMATS.get(format_type, CONSOLE_FORMATS["detailed"])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_file_format(format_type: str) -> str:
|
|
96
|
+
return FILE_FORMATS.get(format_type, FILE_FORMATS["detailed"])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def serialize_json(record: Dict[str, Any]) -> str:
|
|
100
|
+
"""
|
|
101
|
+
JSON 序列化函数
|
|
102
|
+
|
|
103
|
+
将日志记录转换为 JSON 格式
|
|
104
|
+
|
|
105
|
+
使用 orjson(如果可用)提升性能:
|
|
106
|
+
- 比标准库 json 快 2-3 倍
|
|
107
|
+
- 自动处理 datetime、UUID 等类型
|
|
108
|
+
- 更小的内存占用
|
|
109
|
+
"""
|
|
110
|
+
subset = {
|
|
111
|
+
"timestamp": record["time"].isoformat(),
|
|
112
|
+
"level": record["level"].name,
|
|
113
|
+
"logger": record["name"],
|
|
114
|
+
"function": record["function"],
|
|
115
|
+
"line": record["line"],
|
|
116
|
+
"message": record["message"],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# 添加额外字段
|
|
120
|
+
if record.get("extra"):
|
|
121
|
+
subset["extra"] = record["extra"]
|
|
122
|
+
|
|
123
|
+
# 添加异常信息
|
|
124
|
+
if record.get("exception"):
|
|
125
|
+
exc_type, exc_value, exc_tb = record["exception"]
|
|
126
|
+
subset["exception"] = {
|
|
127
|
+
"type": exc_type.__name__ if exc_type else "Unknown",
|
|
128
|
+
"message": str(exc_value),
|
|
129
|
+
# 注意:traceback 应该从 record 的其他字段获取
|
|
130
|
+
# 这里简化处理,实际可能需要格式化 traceback
|
|
131
|
+
}
|
|
132
|
+
# 如果有 traceback 信息,添加进去
|
|
133
|
+
if hasattr(exc_tb, 'tb_frame'):
|
|
134
|
+
subset["exception"]["traceback"] = "".join(
|
|
135
|
+
traceback.format_exception(exc_type, exc_value, exc_tb)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return orjson.dumps(subset, option=orjson.OPT_APPEND_NEWLINE).decode('utf-8')
|