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.
Files changed (143) hide show
  1. infoman/__init__.py +1 -0
  2. infoman/cli/README.md +378 -0
  3. infoman/cli/__init__.py +7 -0
  4. infoman/cli/commands/__init__.py +3 -0
  5. infoman/cli/commands/init.py +312 -0
  6. infoman/cli/scaffold.py +634 -0
  7. infoman/cli/templates/Makefile.template +132 -0
  8. infoman/cli/templates/app/__init__.py.template +3 -0
  9. infoman/cli/templates/app/app.py.template +4 -0
  10. infoman/cli/templates/app/models_base.py.template +18 -0
  11. infoman/cli/templates/app/models_entity_init.py.template +11 -0
  12. infoman/cli/templates/app/models_schemas_init.py.template +11 -0
  13. infoman/cli/templates/app/repository_init.py.template +11 -0
  14. infoman/cli/templates/app/routers_init.py.template +15 -0
  15. infoman/cli/templates/app/services_init.py.template +11 -0
  16. infoman/cli/templates/app/static_index.html.template +39 -0
  17. infoman/cli/templates/app/static_main.js.template +31 -0
  18. infoman/cli/templates/app/static_style.css.template +111 -0
  19. infoman/cli/templates/app/utils_init.py.template +11 -0
  20. infoman/cli/templates/config/.env.dev.template +43 -0
  21. infoman/cli/templates/config/.env.prod.template +43 -0
  22. infoman/cli/templates/config/README.md.template +28 -0
  23. infoman/cli/templates/docker/.dockerignore.template +60 -0
  24. infoman/cli/templates/docker/Dockerfile.template +47 -0
  25. infoman/cli/templates/docker/README.md.template +240 -0
  26. infoman/cli/templates/docker/docker-compose.yml.template +81 -0
  27. infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
  28. infoman/cli/templates/docker/mysql_init.sql.template +15 -0
  29. infoman/cli/templates/project/.env.example.template +1 -0
  30. infoman/cli/templates/project/.gitignore.template +60 -0
  31. infoman/cli/templates/project/Makefile.template +38 -0
  32. infoman/cli/templates/project/README.md.template +137 -0
  33. infoman/cli/templates/project/deploy.sh.template +97 -0
  34. infoman/cli/templates/project/main.py.template +10 -0
  35. infoman/cli/templates/project/manage.sh.template +97 -0
  36. infoman/cli/templates/project/pyproject.toml.template +47 -0
  37. infoman/cli/templates/project/service.sh.template +203 -0
  38. infoman/config/__init__.py +25 -0
  39. infoman/config/base.py +67 -0
  40. infoman/config/db_cache.py +237 -0
  41. infoman/config/db_relation.py +181 -0
  42. infoman/config/db_vector.py +39 -0
  43. infoman/config/jwt.py +16 -0
  44. infoman/config/llm.py +16 -0
  45. infoman/config/log.py +627 -0
  46. infoman/config/mq.py +26 -0
  47. infoman/config/settings.py +65 -0
  48. infoman/llm/__init__.py +0 -0
  49. infoman/llm/llm.py +297 -0
  50. infoman/logger/__init__.py +57 -0
  51. infoman/logger/context.py +191 -0
  52. infoman/logger/core.py +358 -0
  53. infoman/logger/filters.py +157 -0
  54. infoman/logger/formatters.py +138 -0
  55. infoman/logger/handlers.py +276 -0
  56. infoman/logger/metrics.py +160 -0
  57. infoman/performance/README.md +583 -0
  58. infoman/performance/__init__.py +19 -0
  59. infoman/performance/cli.py +215 -0
  60. infoman/performance/config.py +166 -0
  61. infoman/performance/reporter.py +519 -0
  62. infoman/performance/runner.py +303 -0
  63. infoman/performance/standards.py +222 -0
  64. infoman/service/__init__.py +8 -0
  65. infoman/service/app.py +67 -0
  66. infoman/service/core/__init__.py +0 -0
  67. infoman/service/core/auth.py +105 -0
  68. infoman/service/core/lifespan.py +132 -0
  69. infoman/service/core/monitor.py +57 -0
  70. infoman/service/core/response.py +37 -0
  71. infoman/service/exception/__init__.py +7 -0
  72. infoman/service/exception/error.py +274 -0
  73. infoman/service/exception/exception.py +25 -0
  74. infoman/service/exception/handler.py +238 -0
  75. infoman/service/infrastructure/__init__.py +8 -0
  76. infoman/service/infrastructure/base.py +212 -0
  77. infoman/service/infrastructure/db_cache/__init__.py +8 -0
  78. infoman/service/infrastructure/db_cache/manager.py +194 -0
  79. infoman/service/infrastructure/db_relation/__init__.py +41 -0
  80. infoman/service/infrastructure/db_relation/manager.py +300 -0
  81. infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
  82. infoman/service/infrastructure/db_relation/mysql.py +52 -0
  83. infoman/service/infrastructure/db_relation/pgsql.py +54 -0
  84. infoman/service/infrastructure/db_relation/sqllite.py +25 -0
  85. infoman/service/infrastructure/db_vector/__init__.py +40 -0
  86. infoman/service/infrastructure/db_vector/manager.py +201 -0
  87. infoman/service/infrastructure/db_vector/qdrant.py +322 -0
  88. infoman/service/infrastructure/mq/__init__.py +15 -0
  89. infoman/service/infrastructure/mq/manager.py +178 -0
  90. infoman/service/infrastructure/mq/nats/__init__.py +0 -0
  91. infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
  92. infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
  93. infoman/service/launch.py +284 -0
  94. infoman/service/middleware/__init__.py +7 -0
  95. infoman/service/middleware/base.py +41 -0
  96. infoman/service/middleware/logging.py +51 -0
  97. infoman/service/middleware/rate_limit.py +301 -0
  98. infoman/service/middleware/request_id.py +21 -0
  99. infoman/service/middleware/white_list.py +24 -0
  100. infoman/service/models/__init__.py +8 -0
  101. infoman/service/models/base.py +441 -0
  102. infoman/service/models/type/embed.py +70 -0
  103. infoman/service/routers/__init__.py +18 -0
  104. infoman/service/routers/health_router.py +311 -0
  105. infoman/service/routers/monitor_router.py +44 -0
  106. infoman/service/utils/__init__.py +8 -0
  107. infoman/service/utils/cache/__init__.py +0 -0
  108. infoman/service/utils/cache/cache.py +192 -0
  109. infoman/service/utils/module_loader.py +10 -0
  110. infoman/service/utils/parse.py +10 -0
  111. infoman/service/utils/resolver/__init__.py +8 -0
  112. infoman/service/utils/resolver/base.py +47 -0
  113. infoman/service/utils/resolver/resp.py +102 -0
  114. infoman/service/vector/__init__.py +20 -0
  115. infoman/service/vector/base.py +56 -0
  116. infoman/service/vector/qdrant.py +125 -0
  117. infoman/service/vector/service.py +67 -0
  118. infoman/utils/__init__.py +2 -0
  119. infoman/utils/decorators/__init__.py +8 -0
  120. infoman/utils/decorators/cache.py +137 -0
  121. infoman/utils/decorators/retry.py +99 -0
  122. infoman/utils/decorators/safe_execute.py +99 -0
  123. infoman/utils/decorators/timing.py +99 -0
  124. infoman/utils/encryption/__init__.py +8 -0
  125. infoman/utils/encryption/aes.py +66 -0
  126. infoman/utils/encryption/ecc.py +108 -0
  127. infoman/utils/encryption/rsa.py +112 -0
  128. infoman/utils/file/__init__.py +0 -0
  129. infoman/utils/file/handler.py +22 -0
  130. infoman/utils/hash/__init__.py +0 -0
  131. infoman/utils/hash/hash.py +61 -0
  132. infoman/utils/http/__init__.py +8 -0
  133. infoman/utils/http/client.py +62 -0
  134. infoman/utils/http/info.py +94 -0
  135. infoman/utils/http/result.py +19 -0
  136. infoman/utils/notification/__init__.py +8 -0
  137. infoman/utils/notification/feishu.py +35 -0
  138. infoman/utils/text/__init__.py +8 -0
  139. infoman/utils/text/extractor.py +111 -0
  140. infomankit-0.3.23.dist-info/METADATA +632 -0
  141. infomankit-0.3.23.dist-info/RECORD +143 -0
  142. infomankit-0.3.23.dist-info/WHEEL +4 -0
  143. 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')