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/config/log.py ADDED
@@ -0,0 +1,627 @@
1
+ """
2
+ 日志系统配置模块
3
+ 支持:本地文件 + Loki 远程推送 + 动态配置
4
+ """
5
+ from typing import Literal, Optional, Dict, Any
6
+ from pathlib import Path
7
+ from pydantic import Field, field_validator, model_validator, HttpUrl
8
+ from pydantic_settings import BaseSettings, SettingsConfigDict
9
+
10
+
11
+ class LogConfig(BaseSettings):
12
+ """
13
+ 日志系统配置
14
+
15
+ 设计原则:
16
+ 1. 默认值适合开发环境
17
+ 2. 生产环境通过环境变量覆盖
18
+ 3. 自动根据 ENV 调整配置
19
+ 4. 所有配置都有合理的约束
20
+ """
21
+ # ========== 环境 ==========
22
+ ENV: Literal["dev", "test", "prod"] = Field(default="dev")
23
+
24
+ # =================================================================
25
+ # 日志级别配置
26
+ # =================================================================
27
+
28
+ LOG_LEVEL: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
29
+ default="INFO",
30
+ description="全局日志级别"
31
+ )
32
+
33
+ LOG_LEVEL_CONSOLE: Optional[Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]] = Field(
34
+ default=None,
35
+ description="控制台日志级别(None 则使用 LOG_LEVEL)"
36
+ )
37
+
38
+ LOG_LEVEL_FILE: Optional[Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]] = Field(
39
+ default=None,
40
+ description="文件日志级别(None 则使用 LOG_LEVEL)"
41
+ )
42
+
43
+ LOG_LEVEL_LOKI: Optional[Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]] = Field(
44
+ default=None,
45
+ description="Loki 日志级别(None 则使用 LOG_LEVEL)"
46
+ )
47
+
48
+ # =================================================================
49
+ # 日志输出控制
50
+ # =================================================================
51
+
52
+ LOG_ENABLE_CONSOLE: bool = Field(
53
+ default=True,
54
+ description="启用控制台输出"
55
+ )
56
+
57
+ LOG_ENABLE_FILE: bool = Field(
58
+ default=True,
59
+ description="启用文件输出"
60
+ )
61
+
62
+ LOG_ENABLE_LOKI: bool = Field(
63
+ default=False,
64
+ description="启用 Loki 远程推送"
65
+ )
66
+
67
+ # =================================================================
68
+ # 文件存储配置
69
+ # =================================================================
70
+
71
+ LOG_DIR: Path = Field(
72
+ default=Path("./logs"),
73
+ description="日志根目录"
74
+ )
75
+
76
+ LOG_RETENTION: str = Field(
77
+ default="7 days",
78
+ description="日志保留时间(格式: '7 days', '2 weeks', '1 month', '90 days')"
79
+ )
80
+
81
+ LOG_ROTATION: str = Field(
82
+ default="200 MB",
83
+ description="日志轮转大小(格式: '100 MB', '500 MB', '1 GB')"
84
+ )
85
+
86
+ LOG_COMPRESSION: Literal["zip", "gz", "bz2", "xz", None] = Field(
87
+ default="zip",
88
+ description="日志压缩格式(None 表示不压缩)"
89
+ )
90
+
91
+ LOG_FILE_ENCODING: str = Field(
92
+ default="utf-8",
93
+ description="日志文件编码"
94
+ )
95
+
96
+ # =================================================================
97
+ # 日志格式配置
98
+ # =================================================================
99
+
100
+ LOG_FORMAT: Literal["simple", "detailed", "debug", "json", "pro"] = Field(
101
+ default="detailed",
102
+ description="日志格式预设"
103
+ )
104
+
105
+ LOG_ENABLE_CONSOLE_COLOR: bool = Field(
106
+ default=True,
107
+ description="启用控制台颜色输出"
108
+ )
109
+
110
+ LOG_RECORD_CALLER: bool = Field(
111
+ default=True,
112
+ description="记录调用者信息(文件名、函数名、行号)"
113
+ )
114
+
115
+ LOG_RECORD_PROCESS: bool = Field(
116
+ default=False,
117
+ description="记录进程信息(进程 ID、线程 ID)"
118
+ )
119
+
120
+ LOG_ENABLE_BACKTRACE: bool = Field(
121
+ default=True,
122
+ description="启用异常回溯(显示完整调用栈)"
123
+ )
124
+
125
+ LOG_ENABLE_DIAGNOSE: bool = Field(
126
+ default=True,
127
+ description="启用诊断模式(显示变量值)"
128
+ )
129
+
130
+ # =================================================================
131
+ # 上下文配置
132
+ # =================================================================
133
+
134
+ LOG_ENABLE_CONTEXT: bool = Field(
135
+ default=True,
136
+ description="启用上下文管理(request_id, user_id 等)"
137
+ )
138
+
139
+ LOG_CONTEXT_FIELDS: list[str] = Field(
140
+ default=["request_id", "user_id", "trace_id", "span_id"],
141
+ description="默认上下文字段"
142
+ )
143
+
144
+ # =================================================================
145
+ # 性能配置
146
+ # =================================================================
147
+
148
+ LOG_ENABLE_ASYNC: bool = Field(
149
+ default=True,
150
+ description="启用异步日志写入(提升性能)"
151
+ )
152
+
153
+ LOG_QUEUE_SIZE: int = Field(
154
+ default=1000,
155
+ ge=100,
156
+ le=100000,
157
+ description="异步队列大小(100-100000)"
158
+ )
159
+
160
+ # =================================================================
161
+ # Loki 推送配置
162
+ # =================================================================
163
+
164
+ LOG_LOKI_URL: str = Field(
165
+ default="http://loki:3100",
166
+ description="Loki 服务地址"
167
+ )
168
+
169
+ LOG_LOKI_BATCH_SIZE: int = Field(
170
+ default=100,
171
+ ge=10,
172
+ le=1000,
173
+ description="批量推送大小(10-1000)"
174
+ )
175
+
176
+ LOG_LOKI_FLUSH_INTERVAL: float = Field(
177
+ default=5.0,
178
+ ge=1.0,
179
+ le=60.0,
180
+ description="刷新间隔(1-60 秒)"
181
+ )
182
+
183
+ LOG_LOKI_TIMEOUT: float = Field(
184
+ default=10.0,
185
+ ge=1.0,
186
+ le=30.0,
187
+ description="推送超时(1-30 秒)"
188
+ )
189
+
190
+ LOG_LOKI_RETRY_TIMES: int = Field(
191
+ default=3,
192
+ ge=0,
193
+ le=10,
194
+ description="重试次数(0-10)"
195
+ )
196
+
197
+ LOG_LOKI_RETRY_BACKOFF: float = Field(
198
+ default=2.0,
199
+ ge=1.0,
200
+ le=10.0,
201
+ description="重试退避系数(1.0-10.0)"
202
+ )
203
+
204
+ LOG_LOKI_LABELS: Dict[str, str] = Field(
205
+ default_factory=dict,
206
+ description="自定义 Loki Labels(会与默认 labels 合并)"
207
+ )
208
+
209
+ LOG_LOKI_ENABLE_FALLBACK: bool = Field(
210
+ default=True,
211
+ description="启用降级策略(Loki 失败时写备份文件)"
212
+ )
213
+
214
+ LOG_LOKI_FALLBACK_FILE: str = Field(
215
+ default="loki_backup.log",
216
+ description="Loki 备份文件名"
217
+ )
218
+
219
+ # =================================================================
220
+ # 日志过滤配置
221
+ # =================================================================
222
+
223
+ LOG_FILTER_MODULES: list[str] = Field(
224
+ default_factory=lambda: ["uvicorn.access", "uvicorn.error"],
225
+ description="需要过滤的模块列表"
226
+ )
227
+
228
+ LOG_FILTER_KEYWORDS: list[str] = Field(
229
+ default_factory=lambda: ["health_check", "heartbeat"],
230
+ description="需要过滤的关键词列表"
231
+ )
232
+
233
+ LOG_ENABLE_RATE_LIMIT: bool = Field(
234
+ default=False,
235
+ description="启用日志速率限制(防止日志风暴)"
236
+ )
237
+
238
+ LOG_RATE_LIMIT_MESSAGES: int = Field(
239
+ default=100,
240
+ ge=10,
241
+ le=10000,
242
+ description="速率限制:每秒最多日志数(10-10000)"
243
+ )
244
+
245
+ # =================================================================
246
+ # 敏感信息脱敏配置
247
+ # =================================================================
248
+
249
+ LOG_ENABLE_SANITIZE: bool = Field(
250
+ default=True,
251
+ description="启用敏感信息脱敏"
252
+ )
253
+
254
+ LOG_SANITIZE_FIELDS: list[str] = Field(
255
+ default_factory=lambda: [
256
+ "password", "token", "secret", "api_key", "credit_card",
257
+ "ssn", "phone", "email", "access_token", "refresh_token"
258
+ ],
259
+ description="需要脱敏的字段名"
260
+ )
261
+
262
+ LOG_SANITIZE_PATTERN: str = Field(
263
+ default="***REDACTED***",
264
+ description="脱敏替换文本"
265
+ )
266
+
267
+ # =================================================================
268
+ # 分级日志文件配置
269
+ # =================================================================
270
+
271
+ LOG_ENABLE_SPLIT_BY_LEVEL: bool = Field(
272
+ default=True,
273
+ description="按级别分割日志文件(info.log, error.log 等)"
274
+ )
275
+
276
+ LOG_ENABLE_DEBUG_FILE: bool = Field(
277
+ default=False,
278
+ description="启用 debug.log(仅开发环境推荐)"
279
+ )
280
+
281
+ LOG_ENABLE_ALL_FILE: bool = Field(
282
+ default=True,
283
+ description="启用 all.log(包含所有级别)"
284
+ )
285
+
286
+ # =================================================================
287
+ # 监控和告警配置
288
+ # =================================================================
289
+
290
+ LOG_ENABLE_METRICS: bool = Field(
291
+ default=False,
292
+ description="启用日志指标统计(日志数量、错误率等)"
293
+ )
294
+
295
+ LOG_ERROR_THRESHOLD: int = Field(
296
+ default=100,
297
+ ge=1,
298
+ le=10000,
299
+ description="错误日志阈值(超过后触发告警)"
300
+ )
301
+
302
+ LOG_ERROR_WINDOW: int = Field(
303
+ default=60,
304
+ ge=10,
305
+ le=3600,
306
+ description="错误统计窗口(秒)"
307
+ )
308
+
309
+ # =================================================================
310
+ # 验证器
311
+ # =================================================================
312
+
313
+ @field_validator("LOG_DIR", mode="before")
314
+ @classmethod
315
+ def create_log_dir(cls, v) -> Path:
316
+ """自动创建日志目录"""
317
+ log_dir = Path(v)
318
+ log_dir.mkdir(parents=True, exist_ok=True)
319
+ return log_dir
320
+
321
+ @field_validator("LOG_LOKI_URL")
322
+ @classmethod
323
+ def validate_loki_url(cls, v: str) -> str:
324
+ """验证并标准化 Loki URL"""
325
+ if not v:
326
+ return v
327
+
328
+ # 补充协议
329
+ if not v.startswith(("http://", "https://")):
330
+ v = f"http://{v}"
331
+
332
+ # 移除尾部斜杠
333
+ v = v.rstrip("/")
334
+
335
+ # 验证端口
336
+ if ":" not in v.split("//")[1]:
337
+ v = f"{v}:3100" # 默认端口
338
+
339
+ return v
340
+
341
+ @field_validator("LOG_RETENTION")
342
+ @classmethod
343
+ def validate_retention(cls, v: str) -> str:
344
+ """验证保留时间格式"""
345
+ import re
346
+ pattern = r'^\d+\s+(day|days|week|weeks|month|months)s?$'
347
+ if not re.match(pattern, v, re.IGNORECASE):
348
+ raise ValueError(
349
+ f"LOG_RETENTION 格式错误: {v},"
350
+ f"正确格式: '7 days', '2 weeks', '1 month'"
351
+ )
352
+ return v
353
+
354
+ @field_validator("LOG_ROTATION")
355
+ @classmethod
356
+ def validate_rotation(cls, v: str) -> str:
357
+ """验证轮转大小格式"""
358
+ import re
359
+ pattern = r'^\d+\s+(MB|GB|KB)$'
360
+ if not re.match(pattern, v, re.IGNORECASE):
361
+ raise ValueError(
362
+ f"LOG_ROTATION 格式错误: {v},"
363
+ f"正确格式: '100 MB', '1 GB'"
364
+ )
365
+ return v
366
+
367
+ def model_post_init(self, __context):
368
+ """初始化后处理(根据环境调整配置)"""
369
+
370
+ # ========== 开发环境 ==========
371
+ if self.ENV == "dev":
372
+ self.LOG_LEVEL = "DEBUG"
373
+ self.LOG_FORMAT = "simple"
374
+ self.LOG_ENABLE_CONSOLE_COLOR = True
375
+ self.LOG_ENABLE_BACKTRACE = True
376
+ self.LOG_ENABLE_DIAGNOSE = True
377
+ self.LOG_ENABLE_DEBUG_FILE = True
378
+ self.LOG_RETENTION = "3 days"
379
+ self.LOG_ENABLE_LOKI = False
380
+
381
+ # ========== 测试环境 ==========
382
+ elif self.ENV == "test":
383
+ self.LOG_LEVEL = "INFO"
384
+ self.LOG_FORMAT = "detailed"
385
+ self.LOG_RETENTION = "7 days"
386
+
387
+ if not self.LOG_ENABLE_LOKI:
388
+ import warnings
389
+ warnings.warn(
390
+ "测试环境建议启用 Loki: LOG_ENABLE_LOKI=true",
391
+ UserWarning
392
+ )
393
+
394
+ # ========== 预发布环境 ==========
395
+ elif self.ENV == "staging":
396
+ self.LOG_LEVEL = "INFO"
397
+ self.LOG_FORMAT = "pro"
398
+ self.LOG_ENABLE_CONSOLE_COLOR = False
399
+ self.LOG_RETENTION = "14 days"
400
+
401
+ if not self.LOG_ENABLE_LOKI:
402
+ raise ValueError("预发布环境必须启用 Loki: LOG_ENABLE_LOKI=true")
403
+
404
+ # ========== 生产环境 ==========
405
+ elif self.ENV == "prod":
406
+ if self.LOG_LEVEL == "DEBUG":
407
+ self.LOG_LEVEL = "INFO"
408
+
409
+ self.LOG_FORMAT = "json"
410
+ self.LOG_ENABLE_CONSOLE_COLOR = False
411
+ self.LOG_ENABLE_DIAGNOSE = False
412
+ self.LOG_ENABLE_DEBUG_FILE = False
413
+
414
+ if self.LOG_RETENTION == "7 days":
415
+ self.LOG_RETENTION = "30 days"
416
+
417
+ if self.LOG_ROTATION == "200 MB":
418
+ self.LOG_ROTATION = "500 MB"
419
+
420
+ # if not self.LOG_ENABLE_LOKI:
421
+ # raise ValueError("生产环境必须启用 Loki: LOG_ENABLE_LOKI=true")
422
+
423
+ self.LOG_ENABLE_METRICS = True
424
+ self.LOG_ENABLE_SANITIZE = True
425
+
426
+ # 设置分级日志级别
427
+ if self.LOG_LEVEL_CONSOLE is None:
428
+ self.LOG_LEVEL_CONSOLE = self.LOG_LEVEL
429
+
430
+ if self.LOG_LEVEL_FILE is None:
431
+ self.LOG_LEVEL_FILE = self.LOG_LEVEL
432
+
433
+ if self.LOG_LEVEL_LOKI is None:
434
+ self.LOG_LEVEL_LOKI = "INFO" if self.LOG_LEVEL == "DEBUG" else self.LOG_LEVEL
435
+
436
+ # 验证依赖
437
+ if self.LOG_ENABLE_LOKI:
438
+ try:
439
+ import httpx
440
+ except ImportError:
441
+ raise ValueError(
442
+ "启用 Loki 需要安装 httpx: pip install httpx"
443
+ )
444
+
445
+ if self.LOG_ENABLE_ASYNC and self.LOG_QUEUE_SIZE < 100:
446
+ import warnings
447
+ warnings.warn(
448
+ f"异步队列大小过小 ({self.LOG_QUEUE_SIZE}),建议至少 1000",
449
+ UserWarning
450
+ )
451
+
452
+ @model_validator(mode='after')
453
+ def validate_dependencies(self) -> 'LogConfig':
454
+ """验证依赖关系"""
455
+
456
+ # Loki 启用时,检查 httpx
457
+ if self.LOG_ENABLE_LOKI:
458
+ try:
459
+ import httpx
460
+ except ImportError:
461
+ raise ValueError(
462
+ "启用 Loki 需要安装 httpx: pip install httpx"
463
+ )
464
+
465
+ # 异步日志启用时,队列大小必须合理
466
+ if self.LOG_ENABLE_ASYNC and self.LOG_QUEUE_SIZE < 100:
467
+ import warnings
468
+ warnings.warn(
469
+ f"异步队列大小过小 ({self.LOG_QUEUE_SIZE}),建议至少 1000",
470
+ UserWarning
471
+ )
472
+
473
+ return self
474
+
475
+ # =================================================================
476
+ # 辅助方法
477
+ # =================================================================
478
+
479
+ def get_loki_labels(self) -> Dict[str, str]:
480
+ """获取完整的 Loki Labels"""
481
+ import socket
482
+
483
+ default_labels = {
484
+ "app": self.APP_NAME,
485
+ "env": self.ENV,
486
+ "hostname": socket.gethostname(),
487
+ }
488
+
489
+ # 合并自定义 labels
490
+ return {**default_labels, **self.LOG_LOKI_LABELS}
491
+
492
+ def get_log_file_path(self, log_type: str) -> Path:
493
+ """获取日志文件路径"""
494
+ return self.LOG_DIR / f"{log_type}.log"
495
+
496
+ def is_production(self) -> bool:
497
+ """是否生产环境"""
498
+ return self.ENV == "prod"
499
+
500
+ def is_development(self) -> bool:
501
+ """是否开发环境"""
502
+ return self.ENV == "dev"
503
+
504
+ def should_log_to_console(self) -> bool:
505
+ """是否输出到控制台"""
506
+ # 生产环境默认不输出控制台(除非明确启用)
507
+ return self.LOG_ENABLE_CONSOLE
508
+
509
+ # =================================================================
510
+ # Pydantic 配置
511
+ # =================================================================
512
+
513
+ model_config = SettingsConfigDict(
514
+ env_prefix="",
515
+ case_sensitive=True,
516
+ extra="ignore",
517
+ validate_assignment=True, # 赋值时验证
518
+ frozen=False, # 允许修改(用于动态调整)
519
+ )
520
+
521
+
522
+ # =================================================================
523
+ # 导出单例
524
+ # =================================================================
525
+
526
+ _log_config_instance: Optional[LogConfig] = None
527
+
528
+
529
+ def get_log_config() -> LogConfig:
530
+ """获取日志配置单例"""
531
+ global _log_config_instance
532
+ if _log_config_instance is None:
533
+ _log_config_instance = LogConfig()
534
+ return _log_config_instance
535
+
536
+
537
+ def reload_log_config() -> LogConfig:
538
+ """重新加载日志配置"""
539
+ global _log_config_instance
540
+ _log_config_instance = LogConfig()
541
+ return _log_config_instance
542
+
543
+
544
+ # =================================================================
545
+ # 配置验证工具
546
+ # =================================================================
547
+
548
+ def validate_log_config() -> tuple[bool, list[str]]:
549
+ """
550
+ 验证日志配置
551
+
552
+ Returns:
553
+ (is_valid, errors)
554
+ """
555
+ errors = []
556
+
557
+ try:
558
+ config = get_log_config()
559
+
560
+ # 检查日志目录权限
561
+ test_file = config.LOG_DIR / ".write_test"
562
+ try:
563
+ test_file.touch()
564
+ test_file.unlink()
565
+ except Exception as e:
566
+ errors.append(f"日志目录不可写: {config.LOG_DIR} - {e}")
567
+
568
+ # 检查 Loki 连接
569
+ if config.LOG_ENABLE_LOKI:
570
+ try:
571
+ import httpx
572
+ response = httpx.get(
573
+ f"{config.LOG_LOKI_URL}/ready",
574
+ timeout=5.0
575
+ )
576
+ if response.status_code != 200:
577
+ errors.append(f"Loki 服务不可用: {config.LOG_LOKI_URL}")
578
+ except Exception as e:
579
+ errors.append(f"无法连接 Loki: {e}")
580
+
581
+ return len(errors) == 0, errors
582
+
583
+ except Exception as e:
584
+ return False, [f"配置加载失败: {e}"]
585
+
586
+
587
+ # =================================================================
588
+ # 使用示例
589
+ # =================================================================
590
+
591
+ if __name__ == "__main__":
592
+ # 测试配置加载
593
+ config = get_log_config()
594
+
595
+ print("=" * 60)
596
+ print("日志配置信息")
597
+ print("=" * 60)
598
+ print(f"应用名称: {config.APP_NAME}")
599
+ print(f"运行环境: {config.ENV}")
600
+ print(f"日志级别: {config.LOG_LEVEL}")
601
+ print(f"日志目录: {config.LOG_DIR}")
602
+ print(f"日志格式: {config.LOG_FORMAT}")
603
+ print(f"控制台输出: {config.LOG_ENABLE_CONSOLE}")
604
+ print(f"文件输出: {config.LOG_ENABLE_FILE}")
605
+ print(f"Loki 推送: {config.LOG_ENABLE_LOKI}")
606
+
607
+ if config.LOG_ENABLE_LOKI:
608
+ print(f"\nLoki 配置:")
609
+ print(f" URL: {config.LOG_LOKI_URL}")
610
+ print(f" 批量大小: {config.LOG_LOKI_BATCH_SIZE}")
611
+ print(f" 刷新间隔: {config.LOG_LOKI_FLUSH_INTERVAL}s")
612
+ print(f" Labels: {config.get_loki_labels()}")
613
+
614
+ print(f"\n性能配置:")
615
+ print(f" 异步日志: {config.LOG_ENABLE_ASYNC}")
616
+ print(f" 队列大小: {config.LOG_QUEUE_SIZE}")
617
+
618
+ print("\n" + "=" * 60)
619
+
620
+ # 验证配置
621
+ is_valid, errors = validate_log_config()
622
+ if is_valid:
623
+ print("✅ 配置验证通过")
624
+ else:
625
+ print("❌ 配置验证失败:")
626
+ for error in errors:
627
+ print(f" - {error}")
infoman/config/mq.py ADDED
@@ -0,0 +1,26 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8 -*-
3
+
4
+ """
5
+ # Time :2025/12/22 21:39
6
+ # Author :Maxwell
7
+ # Description:
8
+ """
9
+
10
+ from typing import List
11
+ from pydantic import Field
12
+ from pydantic_settings import BaseSettings
13
+
14
+
15
+ class MessageQueueConfig(BaseSettings):
16
+ # ========== NATS ==========
17
+ NATS_ENABLED: bool = Field(default=False, description="是否启用 NATS")
18
+ NATS_SERVERS: List[str] = Field(default=[], description="NATS 服务器列表")
19
+ NATS_NAME: str = Field(default="infoman", description="客户端名称")
20
+ NATS_MAX_RECONNECT_ATTEMPTS: int = Field(default=10, description="最大重连次数")
21
+ NATS_RECONNECT_TIME_WAIT: int = Field(default=2, description="重连等待时间(秒)")
22
+
23
+ @property
24
+ def nats_configured(self) -> bool:
25
+ return self.NATS_ENABLED and len(self.NATS_SERVERS) > 0
26
+