agentic-kit-common 0.0.11__tar.gz → 0.0.21__tar.gz

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 (56) hide show
  1. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/PKG-INFO +7 -1
  2. agentic_kit_common-0.0.21/agentic_kit_common/config/__init__.py +21 -0
  3. agentic_kit_common-0.0.21/agentic_kit_common/config/config_loader.py +311 -0
  4. agentic_kit_common-0.0.21/agentic_kit_common/llm/__init__.py +2 -0
  5. agentic_kit_common-0.0.21/agentic_kit_common/llm/openai.py +33 -0
  6. agentic_kit_common-0.0.21/agentic_kit_common/llm/utils.py +61 -0
  7. agentic_kit_common-0.0.21/agentic_kit_common/log/logger.py +69 -0
  8. agentic_kit_common-0.0.21/agentic_kit_common/mcp/mcp_client.py +344 -0
  9. agentic_kit_common-0.0.21/agentic_kit_common/mongodb/__init__.py +2 -0
  10. agentic_kit_common-0.0.21/agentic_kit_common/mongodb/mongodb_manager.py +294 -0
  11. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/execution.py +1 -1
  12. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/multi_session.py +4 -0
  13. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/schema.py +2 -0
  14. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/session.py +2 -2
  15. agentic_kit_common-0.0.21/agentic_kit_common/redis/redis_pool_manager.py +118 -0
  16. agentic_kit_common-0.0.21/agentic_kit_common/sms/ali_sms_client.py +54 -0
  17. agentic_kit_common-0.0.21/agentic_kit_common/web/__init__.py +0 -0
  18. agentic_kit_common-0.0.21/agentic_kit_common/web/http/__init__.py +0 -0
  19. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common.egg-info/PKG-INFO +7 -1
  20. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common.egg-info/SOURCES.txt +16 -0
  21. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common.egg-info/requires.txt +6 -0
  22. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/setup.py +13 -1
  23. agentic_kit_common-0.0.21/test/__init__.py +0 -0
  24. agentic_kit_common-0.0.21/test/conf.yaml +56 -0
  25. agentic_kit_common-0.0.21/test/test_config_manager.py +29 -0
  26. agentic_kit_common-0.0.21/test/test_mcp_client.py +29 -0
  27. agentic_kit_common-0.0.11/agentic_kit_common/log/logger.py +0 -16
  28. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/README.md +0 -0
  29. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/__init__.py +0 -0
  30. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/log/__init__.py +0 -0
  31. {agentic_kit_common-0.0.11/agentic_kit_common/minio → agentic_kit_common-0.0.21/agentic_kit_common/mcp}/__init__.py +0 -0
  32. {agentic_kit_common-0.0.11/agentic_kit_common/vector → agentic_kit_common-0.0.21/agentic_kit_common/minio}/__init__.py +0 -0
  33. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/minio/minio_manager.py +0 -0
  34. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/__init__.py +0 -0
  35. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/base.py +0 -0
  36. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/orm/manager.py +0 -0
  37. {agentic_kit_common-0.0.11/agentic_kit_common/web → agentic_kit_common-0.0.21/agentic_kit_common/redis}/__init__.py +0 -0
  38. {agentic_kit_common-0.0.11/agentic_kit_common/web/http → agentic_kit_common-0.0.21/agentic_kit_common/sms}/__init__.py +0 -0
  39. {agentic_kit_common-0.0.11/test → agentic_kit_common-0.0.21/agentic_kit_common/vector}/__init__.py +0 -0
  40. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/embedding/__init__.py +0 -0
  41. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/embedding/embedding.py +0 -0
  42. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/manager/__init__.py +0 -0
  43. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/manager/milvus_manager.py +0 -0
  44. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/schema/__init__.py +0 -0
  45. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/schema/base.py +0 -0
  46. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/vector/schema/milvus_schema.py +0 -0
  47. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common/web/http/response.py +0 -0
  48. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common.egg-info/dependency_links.txt +0 -0
  49. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/agentic_kit_common.egg-info/top_level.txt +0 -0
  50. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/setup.cfg +0 -0
  51. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/config.py +0 -0
  52. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/settings.py +0 -0
  53. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/test_embedding.py +0 -0
  54. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/test_minio.py +0 -0
  55. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/test_orm.py +0 -0
  56. {agentic_kit_common-0.0.11 → agentic_kit_common-0.0.21}/test/test_vector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-kit-common
3
- Version: 0.0.11
3
+ Version: 0.0.21
4
4
  Summary: Common utilities and tools for agentic kit ecosystem
5
5
  Home-page:
6
6
  Author: manson
@@ -24,11 +24,17 @@ Requires-Dist: langchain_core
24
24
  Requires-Dist: langgraph
25
25
  Requires-Dist: langchain_community
26
26
  Requires-Dist: langchain_experimental
27
+ Requires-Dist: langchain-openai
28
+ Requires-Dist: langchain_mcp_adapters
27
29
  Requires-Dist: mysql-connector-python
28
30
  Requires-Dist: sqlalchemy
29
31
  Requires-Dist: sqlglot
30
32
  Requires-Dist: pymilvus
31
33
  Requires-Dist: xinference_client
34
+ Requires-Dist: pymongo
35
+ Requires-Dist: redis
36
+ Requires-Dist: aliyun-python-sdk-core
37
+ Requires-Dist: aliyun-python-sdk-dysmsapi
32
38
  Dynamic: author
33
39
  Dynamic: author-email
34
40
  Dynamic: classifier
@@ -0,0 +1,21 @@
1
+ from .config_loader import (
2
+ ConfigManager,
3
+ load_config,
4
+ get_llm_config,
5
+ get_rag_config,
6
+ get_database_config,
7
+ get_full_config,
8
+ reload_config,
9
+ set_config_manager,
10
+ )
11
+
12
+ __all__ = [
13
+ "ConfigManager",
14
+ "set_config_manager",
15
+ "load_config",
16
+ "get_llm_config",
17
+ "get_rag_config",
18
+ "get_database_config",
19
+ "get_full_config",
20
+ "reload_config",
21
+ ]
@@ -0,0 +1,311 @@
1
+ import logging
2
+ import os
3
+ from typing import Any, Dict, Optional
4
+
5
+ import yaml
6
+ from dotenv import load_dotenv, find_dotenv
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ load_dotenv(find_dotenv(usecwd=True))
12
+ _config_root_path = os.getenv("DELTA_CONFIG_PATH", None)
13
+
14
+
15
+ class ConfigManager:
16
+ """
17
+ 配置管理器 - 支持多种配置源
18
+
19
+ 支持的配置方式(优先级从高到低):
20
+ 1. 环境变量(最高优先级)
21
+ 2. 初始化时传入的 config 参数
22
+ 3. 配置文件(config_path 或默认的 conf.yaml)
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ config: Optional[Dict[str, Any]] = None,
28
+ config_path: Optional[str] = None
29
+ ):
30
+ """
31
+ 初始化配置管理器
32
+
33
+ Args:
34
+ config: 直接传入的配置字典(适合外部 SDK 调用)
35
+ 格式示例:
36
+ {
37
+ "BASIC_MODEL": {
38
+ "base_url": "http://...",
39
+ "model": "qwen",
40
+ "api_key": "sk-xxx"
41
+ },
42
+ "VISION_MODEL": {
43
+ "base_url": "http://...",
44
+ "model": "vision",
45
+ "api_key": "sk-xxx"
46
+ },
47
+ "RAG_CLIENT": {
48
+ "base_url": "http://...",
49
+ "timeout": 120
50
+ },
51
+ "DATABASE_CLIENT": {
52
+ "base_url": "http://...",
53
+ "timeout": 120
54
+ },
55
+ "MCP_SETTINGS": {
56
+ "default_servers": {
57
+ "file-processor": {
58
+ "transport": "streamable_http",
59
+ "url": "http://...",
60
+ "enabled_tools": ["convert_to_markdown"]
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ config_path: 配置文件路径(可选)
67
+ - 如果不提供,默认使用项目根目录的 conf.yaml
68
+ - 如果提供,使用指定路径的配置文件
69
+
70
+ Examples:
71
+ # 方式1: 直接传入配置(外部 SDK 推荐)
72
+ config_mgr = ConfigManager(config={
73
+ "BASIC_MODEL": {...},
74
+ "RAG_CLIENT": {...}
75
+ })
76
+
77
+ # 方式2: 指定配置文件路径
78
+ config_mgr = ConfigManager(config_path="/path/to/conf.yaml")
79
+
80
+ # 方式3: 使用默认配置文件(项目根目录/conf.yaml)
81
+ config_mgr = ConfigManager()
82
+ """
83
+ self._external_config = config or {}
84
+ self._config_path = config_path or _config_root_path
85
+ self._config_cache: Optional[Dict[str, Any]] = None
86
+
87
+ def load_config(self, force_reload: bool = False) -> Dict[str, Any]:
88
+ """
89
+ 加载配置(带缓存)
90
+
91
+ Args:
92
+ force_reload: 是否强制重新加载
93
+
94
+ Returns:
95
+ Dict[str, Any]: 完整配置字典
96
+ """
97
+ if self._config_cache is not None and not force_reload:
98
+ return self._config_cache
99
+
100
+ # 如果有外部配置,直接使用
101
+ if self._external_config:
102
+ self._config_cache = self._external_config.copy()
103
+ logger.info("使用外部传入的配置")
104
+ return self._config_cache
105
+
106
+ # 从配置文件加载
107
+ conf_path = self._config_path
108
+
109
+ if not os.path.exists(conf_path):
110
+ logger.warning(f"配置文件不存在: {conf_path},使用空配置")
111
+ self._config_cache = {}
112
+ return self._config_cache
113
+
114
+ try:
115
+ with open(conf_path, 'r', encoding='utf-8') as f:
116
+ config = yaml.safe_load(f) or {}
117
+ self._config_cache = config
118
+ logger.info(f"成功加载配置文件: {conf_path}")
119
+ except Exception as e:
120
+ logger.error(f"加载配置文件失败: {e}")
121
+ self._config_cache = {}
122
+
123
+ return self._config_cache
124
+
125
+ def get_llm_config(self, model_type: str = "BASIC_MODEL") -> Dict[str, Any]:
126
+ """
127
+ 获取LLM配置
128
+
129
+ Args:
130
+ model_type: 模型类型,可选 "BASIC_MODEL", "VISION_MODEL"
131
+
132
+ Returns:
133
+ Dict[str, Any]: LLM配置
134
+ """
135
+ config = self.load_config()
136
+ base_config = config.get(model_type, {}).copy() if isinstance(config.get(model_type), dict) else {}
137
+
138
+ # 环境变量覆盖(优先级最高)
139
+ prefix = f"{model_type}__"
140
+ for key, value in os.environ.items():
141
+ if key.startswith(prefix):
142
+ conf_key = key[len(prefix):].lower()
143
+ base_config[conf_key] = value
144
+
145
+ return base_config
146
+
147
+ def get_rag_config(self) -> Dict[str, Any]:
148
+ """
149
+ 获取RAG客户端配置
150
+
151
+ Returns:
152
+ Dict[str, Any]: RAG配置
153
+ """
154
+ config = self.load_config()
155
+ rag_config = config.get("RAG_CLIENT", {}).copy() if isinstance(config.get("RAG_CLIENT"), dict) else {}
156
+
157
+ # 支持环境变量覆盖
158
+ env_overrides = {
159
+ "base_url": os.getenv("RAG_CLIENT__base_url"),
160
+ "timeout": os.getenv("RAG_CLIENT__timeout"),
161
+ "max_retries": os.getenv("RAG_CLIENT__max_retries"),
162
+ }
163
+
164
+ for key, value in env_overrides.items():
165
+ if value is not None:
166
+ rag_config[key] = value
167
+
168
+ return rag_config
169
+
170
+ def get_mcp_config(self) -> Dict[str, Any]:
171
+ """
172
+ 获取MCP服务器配置
173
+
174
+ Returns:
175
+ Dict[str, Any]: MCP配置
176
+ """
177
+ config = self.load_config()
178
+ return config.get("MCP_SETTINGS", {})
179
+
180
+ def get_database_config(self) -> Dict[str, Any]:
181
+ """
182
+ 获取数据库客户端配置
183
+
184
+ Returns:
185
+ Dict[str, Any]: 数据库配置
186
+ """
187
+ config = self.load_config()
188
+ db_config = config.get("DATABASE_CLIENT", {}).copy() if isinstance(config.get("DATABASE_CLIENT"), dict) else {}
189
+
190
+ # 支持环境变量覆盖
191
+ env_overrides = {
192
+ "base_url": os.getenv("DATABASE_CLIENT__base_url"),
193
+ "timeout": os.getenv("DATABASE_CLIENT__timeout"),
194
+ }
195
+
196
+ for key, value in env_overrides.items():
197
+ if value is not None:
198
+ db_config[key] = value
199
+
200
+ return db_config
201
+
202
+ def get_full_config(self) -> Dict[str, Any]:
203
+ """
204
+ 获取完整配置
205
+
206
+ Returns:
207
+ Dict[str, Any]: 完整配置字典
208
+ """
209
+ return self.load_config()
210
+
211
+ def reload_config(self):
212
+ """重新加载配置(清除缓存)"""
213
+ self._config_cache = None
214
+ logger.info("配置已重新加载")
215
+
216
+
217
+ # ============================================================================
218
+ # 全局默认配置管理器(向后兼容)
219
+ # ============================================================================
220
+
221
+ _default_config_manager: Optional[ConfigManager] = None
222
+
223
+
224
+ def _get_default_config_manager() -> ConfigManager:
225
+ """获取默认配置管理器实例"""
226
+ global _default_config_manager
227
+ if _default_config_manager is None:
228
+ _default_config_manager = ConfigManager()
229
+ return _default_config_manager
230
+
231
+
232
+ def set_config_manager(config_manager: ConfigManager):
233
+ """
234
+ 设置全局配置管理器(用于外部 SDK)
235
+
236
+ Args:
237
+ config_manager: ConfigManager 实例
238
+
239
+ Example:
240
+ # 在外部 SDK 中设置自定义配置
241
+ config_mgr = ConfigManager(config={...})
242
+ set_config_manager(config_mgr)
243
+ """
244
+ global _default_config_manager
245
+ _default_config_manager = config_manager
246
+ logger.info("已设置全局配置管理器")
247
+
248
+
249
+ # ============================================================================
250
+ # 向后兼容的函数接口
251
+ # ============================================================================
252
+
253
+ def load_config(force_reload: bool = False) -> Dict[str, Any]:
254
+ """
255
+ 加载配置文件(带缓存)
256
+
257
+ Args:
258
+ force_reload: 是否强制重新加载
259
+
260
+ Returns:
261
+ Dict[str, Any]: 完整配置字典
262
+ """
263
+ return _get_default_config_manager().load_config(force_reload)
264
+
265
+
266
+ def get_llm_config(model_type: str = "BASIC_MODEL") -> Dict[str, Any]:
267
+ """
268
+ 获取LLM配置
269
+
270
+ Args:
271
+ model_type: 模型类型,可选 "BASIC_MODEL", "VISION_MODEL"
272
+
273
+ Returns:
274
+ Dict[str, Any]: LLM配置
275
+ """
276
+ return _get_default_config_manager().get_llm_config(model_type)
277
+
278
+
279
+ def get_rag_config() -> Dict[str, Any]:
280
+ """
281
+ 获取RAG客户端配置
282
+
283
+ Returns:
284
+ Dict[str, Any]: RAG配置
285
+ """
286
+ return _get_default_config_manager().get_rag_config()
287
+
288
+
289
+ def get_database_config() -> Dict[str, Any]:
290
+ """
291
+ 获取数据库客户端配置
292
+
293
+ Returns:
294
+ Dict[str, Any]: 数据库配置
295
+ """
296
+ return _get_default_config_manager().get_database_config()
297
+
298
+
299
+ def get_full_config() -> Dict[str, Any]:
300
+ """
301
+ 获取完整配置
302
+
303
+ Returns:
304
+ Dict[str, Any]: 完整配置字典
305
+ """
306
+ return _get_default_config_manager().get_full_config()
307
+
308
+
309
+ def reload_config():
310
+ """重新加载配置(清除缓存)"""
311
+ _get_default_config_manager().reload_config()
@@ -0,0 +1,2 @@
1
+ from .openai import create_openai_llm
2
+ from .utils import combine_simple_context
@@ -0,0 +1,33 @@
1
+ import os
2
+
3
+ from dotenv import load_dotenv, find_dotenv
4
+ from langchain_openai import ChatOpenAI
5
+
6
+ load_dotenv(find_dotenv(usecwd=True))
7
+
8
+
9
+ model_name = os.getenv("MODEL_NAME", None)
10
+ openai_api_base = os.getenv("OPENAI_API_BASE", None)
11
+ openai_api_key = os.getenv("OPENAI_API_KEY", 'API_KEY')
12
+ temperature = float(os.getenv("TEMPERATURE", 0.2))
13
+
14
+
15
+ def create_openai_llm(**kwargs):
16
+ _model_name = kwargs.pop('model_name', model_name)
17
+ _openai_api_base = kwargs.pop('openai_api_base', openai_api_base)
18
+ _openai_api_key = kwargs.pop('openai_api_key', openai_api_key)
19
+ if not _openai_api_key:
20
+ _openai_api_key = 'API_KEY'
21
+ _temperature = kwargs.pop('temperature', temperature)
22
+
23
+ assert model_name is not None
24
+ assert openai_api_base is not None
25
+
26
+ llm = ChatOpenAI(
27
+ model_name=_model_name,
28
+ openai_api_base=_openai_api_base,
29
+ openai_api_key=_openai_api_key,
30
+ temperature=_temperature,
31
+ **kwargs
32
+ )
33
+ return llm
@@ -0,0 +1,61 @@
1
+ import json
2
+ import re
3
+ from typing import Any, Dict, List, Union
4
+
5
+ from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
6
+
7
+
8
+ def combine_simple_context(system: str, user: str = None):
9
+ context: List[Union[BaseMessage, dict[str, Any]]] = [SystemMessage(content=system)]
10
+ if user:
11
+ context.append(HumanMessage(content=system))
12
+ return context
13
+
14
+
15
+ def fix_json_response(text: str) -> Union[Dict, List, str, int, float, bool, None]:
16
+ """
17
+ 去掉大模型返回的 ```json / ``` 等 markdown 标记,并安全反序列化 JSON。
18
+ 若解析失败,返回原字符串。
19
+
20
+ 参数
21
+ ----
22
+ text : str
23
+ 原始响应,可能包含 ```json ... ``` 或其他变体。
24
+
25
+ 返回
26
+ ----
27
+ Python 对象(dict / list / str / int / float / bool / None)
28
+ 解析失败时返回输入字符串本身。
29
+ """
30
+ if not isinstance(text, str):
31
+ return text
32
+
33
+ # 1. 去掉 ```json 或 ``` 包裹(支持开头、结尾、单行、多行)
34
+ cleaned = re.sub(r'^\s*```(?:json|JSON)?\s*\n?', '', text)
35
+ cleaned = re.sub(r'\n?\s*```\s*$', '', cleaned)
36
+
37
+ # 2. 去掉首尾空白
38
+ cleaned = cleaned.strip()
39
+
40
+ # 3. 尝试 JSON 反序列化
41
+ try:
42
+ json.loads(cleaned)
43
+ return cleaned
44
+ except json.JSONDecodeError:
45
+ # 4. 兜底:返回原字符串
46
+ return text
47
+
48
+
49
+ # ----------------- 使用示例 -----------------
50
+ if __name__ == "__main__":
51
+ demo_list = [
52
+ '```json\n{"a": 1, "b": "hello"}\n```',
53
+ "```JSON{'key': 'value'}```",
54
+ "```\n[1, 2, 3]```",
55
+ "plain text",
56
+ {"already_dict": 1},
57
+ ]
58
+ for d in demo_list:
59
+ print("原始:", repr(d))
60
+ print("修复:", repr(fix_json_response(d)))
61
+ print("-" * 30)
@@ -0,0 +1,69 @@
1
+ import logging
2
+ import os
3
+ import uuid
4
+ from logging.handlers import TimedRotatingFileHandler
5
+ from pathlib import Path
6
+ from typing import Union
7
+
8
+ from dotenv import load_dotenv, find_dotenv
9
+
10
+ load_dotenv(find_dotenv(usecwd=True))
11
+
12
+
13
+ logger_root_path = os.getenv("LOGGER_ROOT_PATH", None)
14
+ log_dir = None
15
+
16
+ if logger_root_path:
17
+ if logger_root_path.endswith('/'):
18
+ log_dir = f"{logger_root_path}logs"
19
+ else:
20
+ log_dir = f"{logger_root_path}/logs"
21
+ Path(log_dir).mkdir(parents=True, exist_ok=True)
22
+
23
+
24
+ class LogUtils:
25
+ _handlers = {}
26
+
27
+ # 日志文件根据日期创建
28
+ # 根据参数name,将message写入对应的日志文件内
29
+ @classmethod
30
+ def log(cls, name: str="log", message: str="", log_uuid: str = str(uuid.uuid4()), level: Union[int, str] = logging.INFO) -> None:
31
+ if not log_dir:
32
+ return
33
+
34
+ try:
35
+ logger = logging.getLogger(name)
36
+ logger.setLevel(level)
37
+ logger.propagate = False
38
+ if name not in cls._handlers:
39
+ # 保证日志文件存在
40
+ # LOG_DIR.mkdir(parents=True, exist_ok=True)
41
+ # print(f"Log directory created at: {LOG_DIR}")
42
+ handler = TimedRotatingFileHandler(f'{log_dir}/{name}.log', # 基础文件名
43
+ when='midnight', # 每天午夜
44
+ interval=1,
45
+ backupCount=7, # 保留7天
46
+ encoding='utf-8'
47
+ )
48
+ formatter = logging.Formatter('%(uuid)s - %(asctime)s - %(name)s - %(levelname)s - %(message)s')
49
+ handler.setFormatter(formatter)
50
+ handler.setLevel(level)
51
+
52
+ # 控制台日志
53
+ stream_handler = logging.StreamHandler()
54
+ stream_handler.setLevel(level)
55
+ stream_formatter = logging.Formatter('%(uuid)s - %(asctime)s - %(name)s - %(levelname)s - %(message)s')
56
+ stream_handler.setFormatter(stream_formatter)
57
+ if not logger.hasHandlers():
58
+ logger.addHandler(handler)
59
+ logger.addHandler(stream_handler)
60
+ cls._handlers[name] = handler
61
+
62
+ req_id = log_uuid or str(uuid.uuid4())
63
+ logger.log(level, message, extra={'uuid': req_id}) # 使用UUID
64
+
65
+ for handler in logger.handlers:
66
+ handler.flush()
67
+ except Exception as e:
68
+ # note: ignore error
69
+ pass