full-stack-coding-assistant-agent 0.1.0__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.
- agents/__init__.py +0 -0
- agents/audit_agent.py +223 -0
- agents/backend_agent.py +179 -0
- agents/base_agent.py +406 -0
- agents/frontend_agent.py +148 -0
- agents/test_agent.py +155 -0
- coordinator/__init__.py +0 -0
- coordinator/coordinator.py +452 -0
- coordinator/dag.py +147 -0
- executor/__init__.py +0 -0
- executor/cb_integration.py +160 -0
- full_stack_coding_assistant_agent/__init__.py +6 -0
- full_stack_coding_assistant_agent/cli.py +10 -0
- full_stack_coding_assistant_agent/main.py +686 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/METADATA +849 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/RECORD +31 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/WHEEL +5 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/entry_points.txt +2 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/top_level.txt +7 -0
- model/__init__.py +0 -0
- model/config.py +62 -0
- model/model_router.py +150 -0
- storage/__init__.py +0 -0
- storage/context_db.py +274 -0
- utils/__init__.py +0 -0
- utils/agent_selector.py +243 -0
- utils/config_validator.py +143 -0
- utils/logger.py +95 -0
- utils/output_manager.py +1572 -0
- utils/pdf_reader.py +122 -0
- utils/version.py +188 -0
utils/agent_selector.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent 智能选择器 - 基于 LLM 分析用户需求,判断需要运行哪些 Agent
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
from model.model_router import ModelRouter
|
|
10
|
+
from utils.logger import error, info, warning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentSelector:
|
|
14
|
+
"""
|
|
15
|
+
智能选择需要运行的 Agent
|
|
16
|
+
|
|
17
|
+
使用轻量模型分析用户需求,输出需要运行的 Agent 列表。
|
|
18
|
+
同时处理依赖修正(如选中 frontend 时必须也选中 backend)。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# 所有可用的 Agent 类型
|
|
22
|
+
ALL_AGENTS = ["backend", "frontend", "test", "audit"]
|
|
23
|
+
|
|
24
|
+
# Agent 中文描述(用于 prompt)
|
|
25
|
+
AGENT_DESCRIPTIONS = {
|
|
26
|
+
"backend": "后端开发(生成/修改 API 接口、数据库、业务逻辑)",
|
|
27
|
+
"frontend": "前端开发(生成/修改 UI 组件、页面、样式)",
|
|
28
|
+
"test": "测试(生成/修改单元测试、集成测试、E2E 测试)",
|
|
29
|
+
"audit": "代码审计(检查代码质量、安全漏洞、性能问题)",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def __init__(self, model_router: ModelRouter):
|
|
33
|
+
"""
|
|
34
|
+
初始化选择器
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
model_router: 模型路由器实例(使用轻量模型以节省成本)
|
|
38
|
+
"""
|
|
39
|
+
self.model_router = model_router
|
|
40
|
+
# 使用轻量模型进行 Agent 选择
|
|
41
|
+
self.model = model_router.get_model_for_agent("frontend") # frontend 用轻量模型
|
|
42
|
+
|
|
43
|
+
def select_agents(
|
|
44
|
+
self,
|
|
45
|
+
user_input: str,
|
|
46
|
+
existing_code_summary: str = "",
|
|
47
|
+
) -> Dict:
|
|
48
|
+
"""
|
|
49
|
+
分析用户需求,返回需要运行的 Agent 列表
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
user_input: 用户输入的需求描述
|
|
53
|
+
existing_code_summary: 已有代码的摘要(可选)
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
字典 {
|
|
57
|
+
"agents": ["backend", "frontend"],
|
|
58
|
+
"reason": "需要修改登录接口和登录页面"
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
prompt = self._build_prompt(user_input, existing_code_summary)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
messages = [
|
|
65
|
+
{"role": "system", "content": self._get_system_prompt()},
|
|
66
|
+
{"role": "user", "content": prompt},
|
|
67
|
+
]
|
|
68
|
+
response = self.model_router.chat(
|
|
69
|
+
messages=messages,
|
|
70
|
+
model=self.model,
|
|
71
|
+
temperature=0.1, # 低温度,确保输出稳定
|
|
72
|
+
max_tokens=512,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result = self._parse_response(response["content"])
|
|
76
|
+
result = self._fix_dependencies(result)
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
error(f"Agent 选择失败,使用默认全部 Agent: {e}")
|
|
81
|
+
return {
|
|
82
|
+
"agents": self.ALL_AGENTS,
|
|
83
|
+
"reason": f"LLM 调用失败,默认运行全部 Agent: {e}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def _get_system_prompt(self) -> str:
|
|
87
|
+
"""获取系统提示词"""
|
|
88
|
+
return """你是一个智能任务分析器。你的任务是分析用户的需求描述,判断需要运行哪些智能体(Agent)来完成任务。
|
|
89
|
+
|
|
90
|
+
请以严格的 JSON 格式输出结果,不要输出任何 JSON 以外的内容。"""
|
|
91
|
+
|
|
92
|
+
def _build_prompt(self, user_input: str, existing_code_summary: str) -> str:
|
|
93
|
+
"""构建用户提示词"""
|
|
94
|
+
agent_list = "\n".join(
|
|
95
|
+
f"- {name}: {desc}" for name, desc in self.AGENT_DESCRIPTIONS.items()
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
prompt = f"""分析以下用户需求,判断需要运行哪些智能体(Agent):
|
|
99
|
+
|
|
100
|
+
## 用户需求
|
|
101
|
+
{user_input}
|
|
102
|
+
|
|
103
|
+
## 可选 Agent
|
|
104
|
+
{agent_list}
|
|
105
|
+
|
|
106
|
+
## 输出要求
|
|
107
|
+
请以以下 JSON 格式输出(只输出 JSON,不要有任何其他文字):
|
|
108
|
+
```json
|
|
109
|
+
{{
|
|
110
|
+
"agents": ["backend", "frontend"],
|
|
111
|
+
"reason": "需要修改登录接口和登录页面"
|
|
112
|
+
}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 选择规则
|
|
116
|
+
1. 如果需求涉及后端逻辑、API、数据库,选择 backend
|
|
117
|
+
2. 如果需求涉及前端页面、UI、交互,选择 frontend
|
|
118
|
+
3. 如果需求明确提到测试、测试用例,选择 test
|
|
119
|
+
4. 如果需求提到代码审查、安全检查、性能优化,选择 audit
|
|
120
|
+
5. 如果不确定,选择全部 Agent
|
|
121
|
+
6. frontend 依赖 backend 的输出(API 契约),如果选中 frontend 建议也选中 backend
|
|
122
|
+
7. test 依赖 backend 和 frontend 的代码,如果选中 test 建议也选中 backend 和 frontend
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
if existing_code_summary:
|
|
126
|
+
prompt += f"\n## 已有代码摘要\n{existing_code_summary}\n"
|
|
127
|
+
|
|
128
|
+
prompt += "\n请只输出 JSON,不要有任何其他文字:\n"
|
|
129
|
+
return prompt
|
|
130
|
+
|
|
131
|
+
def _parse_response(self, response: str) -> Dict:
|
|
132
|
+
"""
|
|
133
|
+
解析 LLM 返回的 JSON
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
response: LLM 返回的文本
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
解析后的字典,失败时返回默认全部 Agent
|
|
140
|
+
"""
|
|
141
|
+
if not response:
|
|
142
|
+
warning("Agent 选择器收到空响应,使用默认全部 Agent")
|
|
143
|
+
return {
|
|
144
|
+
"agents": self.ALL_AGENTS,
|
|
145
|
+
"reason": "LLM 返回空响应",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# 尝试提取 JSON(可能被 ```json ``` 包裹)
|
|
149
|
+
json_match = re.search(r"\{.*\}", response, re.DOTALL)
|
|
150
|
+
if not json_match:
|
|
151
|
+
warning(f"无法从响应中解析 JSON: {response[:200]}")
|
|
152
|
+
return {
|
|
153
|
+
"agents": self.ALL_AGENTS,
|
|
154
|
+
"reason": "无法解析 LLM 响应,默认运行全部 Agent",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
data = json.loads(json_match.group())
|
|
159
|
+
|
|
160
|
+
# 验证 agents 字段
|
|
161
|
+
agents = data.get("agents", [])
|
|
162
|
+
if not isinstance(agents, list) or len(agents) == 0:
|
|
163
|
+
warning(f"agents 字段无效: {agents}")
|
|
164
|
+
agents = self.ALL_AGENTS
|
|
165
|
+
|
|
166
|
+
# 过滤掉未知的 Agent 类型
|
|
167
|
+
agents = [a for a in agents if a in self.ALL_AGENTS]
|
|
168
|
+
if not agents:
|
|
169
|
+
agents = self.ALL_AGENTS
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
"agents": agents,
|
|
173
|
+
"reason": data.get("reason", "用户需求涉及多个方面"),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
except json.JSONDecodeError as e:
|
|
177
|
+
warning(f"JSON 解析失败: {e}, 响应: {response[:200]}")
|
|
178
|
+
return {
|
|
179
|
+
"agents": self.ALL_AGENTS,
|
|
180
|
+
"reason": "JSON 解析失败,默认运行全部 Agent",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
def _fix_dependencies(self, result: Dict) -> Dict:
|
|
184
|
+
"""
|
|
185
|
+
修正依赖关系:
|
|
186
|
+
- 如果选中 frontend 但未选中 backend,自动添加 backend
|
|
187
|
+
- 如果选中 test 但未选中 backend/frontend,自动添加
|
|
188
|
+
- 如果选中 audit 但未选中其他,也添加(audit 需要代码来审计)
|
|
189
|
+
"""
|
|
190
|
+
agents = set(result["agents"])
|
|
191
|
+
|
|
192
|
+
# frontend 依赖 backend(需要 API 契约)
|
|
193
|
+
if "frontend" in agents and "backend" not in agents:
|
|
194
|
+
info("依赖修正: frontend 需要 backend,自动添加 backend")
|
|
195
|
+
agents.add("backend")
|
|
196
|
+
|
|
197
|
+
# test 依赖 backend 和 frontend
|
|
198
|
+
if "test" in agents:
|
|
199
|
+
if "backend" not in agents:
|
|
200
|
+
info("依赖修正: test 需要 backend,自动添加 backend")
|
|
201
|
+
agents.add("backend")
|
|
202
|
+
if "frontend" not in agents:
|
|
203
|
+
info("依赖修正: test 需要 frontend,自动添加 frontend")
|
|
204
|
+
agents.add("frontend")
|
|
205
|
+
|
|
206
|
+
# audit 需要代码来审计
|
|
207
|
+
if "audit" in agents and len(agents) == 1:
|
|
208
|
+
info("依赖修正: audit 需要代码来审计,自动添加 backend 和 frontend")
|
|
209
|
+
agents.add("backend")
|
|
210
|
+
agents.add("frontend")
|
|
211
|
+
|
|
212
|
+
result["agents"] = sorted(agents)
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def format_existing_code_summary(output_dir: Optional[str]) -> str:
|
|
217
|
+
"""
|
|
218
|
+
生成已有代码的简短摘要(用于注入到选择器的 prompt)
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
output_dir: 输出目录路径
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
代码摘要字符串(文件名列表)
|
|
225
|
+
"""
|
|
226
|
+
if not output_dir:
|
|
227
|
+
return ""
|
|
228
|
+
|
|
229
|
+
from pathlib import Path
|
|
230
|
+
|
|
231
|
+
path = Path(output_dir)
|
|
232
|
+
if not path.exists():
|
|
233
|
+
return ""
|
|
234
|
+
|
|
235
|
+
parts = []
|
|
236
|
+
for sub_dir in ["backend", "frontend", "tests"]:
|
|
237
|
+
target = path / sub_dir
|
|
238
|
+
if target.exists():
|
|
239
|
+
files = [f.name for f in target.iterdir() if f.is_file()]
|
|
240
|
+
if files:
|
|
241
|
+
parts.append(f"{sub_dir}: {', '.join(files[:5])}")
|
|
242
|
+
|
|
243
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
配置验证工具 - 检查必需的环境变量和配置
|
|
3
|
+
支持 Python 3.13+ 的类型检查
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import List, Tuple
|
|
9
|
+
|
|
10
|
+
from utils.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigValidator:
|
|
16
|
+
"""配置验证器"""
|
|
17
|
+
|
|
18
|
+
# 必需的环境变量
|
|
19
|
+
REQUIRED_ENVS = [
|
|
20
|
+
"TENCENT_API_KEY",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# 可选的环境变量及其默认值
|
|
24
|
+
OPTIONAL_ENVS = {
|
|
25
|
+
"TENCENT_API_BASE": "https://api.hunyuan.cloud.tencent.com/hyllm/v1",
|
|
26
|
+
"SQLITE_DB_PATH": "./context.db",
|
|
27
|
+
"CODEBUDDY_CLI_PATH": "codebuddy",
|
|
28
|
+
"CODEBUDDY_TIMEOUT": "60",
|
|
29
|
+
"LOG_LEVEL": "INFO",
|
|
30
|
+
"DEBUG": "false",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# API Key 格式验证
|
|
34
|
+
API_KEY_PATTERNS = {
|
|
35
|
+
"TENCENT_API_KEY": r"^sk-", # 腾讯混元 API Key 通常以 sk- 开头
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_envs(cls, exit_on_error: bool = True) -> Tuple[bool, List[str]]:
|
|
40
|
+
"""
|
|
41
|
+
验证环境变量配置
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
exit_on_error: 验证失败时是否退出程序
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
(是否通过验证, 错误信息列表)
|
|
48
|
+
"""
|
|
49
|
+
errors = []
|
|
50
|
+
warnings = []
|
|
51
|
+
|
|
52
|
+
# 检查必需的环境变量
|
|
53
|
+
for env_name in cls.REQUIRED_ENVS:
|
|
54
|
+
value = os.getenv(env_name)
|
|
55
|
+
if not value or value == f"YOUR_{env_name}_HERE":
|
|
56
|
+
errors.append(f"缺少必需的环境变量: {env_name}")
|
|
57
|
+
else:
|
|
58
|
+
# 验证 API Key 格式
|
|
59
|
+
if env_name in cls.API_KEY_PATTERNS:
|
|
60
|
+
import re
|
|
61
|
+
|
|
62
|
+
pattern = cls.API_KEY_PATTERNS[env_name]
|
|
63
|
+
if not re.match(pattern, value):
|
|
64
|
+
warnings.append(f"{env_name} 格式可能不正确(期望以 sk- 开头)")
|
|
65
|
+
|
|
66
|
+
# 检查可选的环境变量
|
|
67
|
+
for env_name, default_value in cls.OPTIONAL_ENVS.items():
|
|
68
|
+
value = os.getenv(env_name)
|
|
69
|
+
if not value:
|
|
70
|
+
warnings.append(
|
|
71
|
+
f"可选环境变量 {env_name} 未设置,将使用默认值: {default_value}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# 输出结果
|
|
75
|
+
if warnings:
|
|
76
|
+
for warning in warnings:
|
|
77
|
+
logger.warning(warning)
|
|
78
|
+
|
|
79
|
+
if errors:
|
|
80
|
+
for error in errors:
|
|
81
|
+
logger.error(error)
|
|
82
|
+
|
|
83
|
+
if exit_on_error:
|
|
84
|
+
logger.error("配置验证失败,程序退出")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
return False, errors
|
|
87
|
+
|
|
88
|
+
logger.info("✅ 配置验证通过")
|
|
89
|
+
return True, []
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def print_config_summary(cls):
|
|
93
|
+
"""打印配置摘要"""
|
|
94
|
+
print("=" * 60)
|
|
95
|
+
print("配置摘要")
|
|
96
|
+
print("=" * 60)
|
|
97
|
+
|
|
98
|
+
# 必需的环境变量
|
|
99
|
+
print("\n【必需配置】")
|
|
100
|
+
for env_name in cls.REQUIRED_ENVS:
|
|
101
|
+
value = os.getenv(env_name)
|
|
102
|
+
if value and value != f"YOUR_{env_name}_HERE":
|
|
103
|
+
# 隐藏 API Key 的大部分内容
|
|
104
|
+
if "API_KEY" in env_name:
|
|
105
|
+
masked_value = value[:10] + "*" * (len(value) - 10)
|
|
106
|
+
print(f" {env_name}: {masked_value}")
|
|
107
|
+
else:
|
|
108
|
+
print(f" {env_name}: {value}")
|
|
109
|
+
else:
|
|
110
|
+
print(f" {env_name}: ❌ 未配置")
|
|
111
|
+
|
|
112
|
+
# 可选的环境变量
|
|
113
|
+
print("\n【可选配置】")
|
|
114
|
+
for env_name, default_value in cls.OPTIONAL_ENVS.items():
|
|
115
|
+
value = os.getenv(env_name, default_value)
|
|
116
|
+
print(f" {env_name}: {value}")
|
|
117
|
+
|
|
118
|
+
print("=" * 60)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def validate_config(exit_on_error: bool = True) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
便捷函数:验证配置
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
exit_on_error: 验证失败时是否退出程序
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
是否通过验证
|
|
130
|
+
"""
|
|
131
|
+
return ConfigValidator.validate_envs(exit_on_error)[0]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def print_config():
|
|
135
|
+
"""便捷函数:打印配置摘要"""
|
|
136
|
+
ConfigValidator.print_config_summary()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
# 单独运行此脚本时,打印配置摘要
|
|
141
|
+
print_config()
|
|
142
|
+
print("\n" + "=" * 60)
|
|
143
|
+
validate_config(exit_on_error=False)
|
utils/logger.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
日志工具 - 提供统一的日志记录功能
|
|
3
|
+
支持 Python 3.13+ 的最新日志特性
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_logger(
|
|
12
|
+
name: str = "coding_agent",
|
|
13
|
+
log_level: Optional[str] = None,
|
|
14
|
+
log_file: Optional[str] = None,
|
|
15
|
+
) -> logging.Logger:
|
|
16
|
+
"""
|
|
17
|
+
设置日志记录器
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: 日志记录器名称
|
|
21
|
+
log_level: 日志级别 (DEBUG/INFO/WARNING/ERROR)
|
|
22
|
+
log_file: 日志文件路径
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
配置好的 Logger 实例
|
|
26
|
+
"""
|
|
27
|
+
# 从环境变量读取配置
|
|
28
|
+
if log_level is None:
|
|
29
|
+
log_level = os.getenv("LOG_LEVEL", "INFO")
|
|
30
|
+
|
|
31
|
+
if log_file is None:
|
|
32
|
+
log_file = os.getenv("LOG_FILE", "logs/app.log")
|
|
33
|
+
|
|
34
|
+
# 创建日志记录器
|
|
35
|
+
logger = logging.getLogger(name)
|
|
36
|
+
logger.setLevel(getattr(logging, log_level.upper()))
|
|
37
|
+
|
|
38
|
+
# 避免重复添加 handler
|
|
39
|
+
if logger.handlers:
|
|
40
|
+
return logger
|
|
41
|
+
|
|
42
|
+
# 创建日志目录
|
|
43
|
+
log_dir = os.path.dirname(log_file)
|
|
44
|
+
if log_dir and not os.path.exists(log_dir):
|
|
45
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# 日志格式(Python 3.13+ 优化)
|
|
48
|
+
formatter = logging.Formatter(
|
|
49
|
+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
50
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 文件 Handler(带轮转)
|
|
54
|
+
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
55
|
+
file_handler.setLevel(getattr(logging, log_level.upper()))
|
|
56
|
+
file_handler.setFormatter(formatter)
|
|
57
|
+
logger.addHandler(file_handler)
|
|
58
|
+
|
|
59
|
+
# 控制台 Handler
|
|
60
|
+
console_handler = logging.StreamHandler()
|
|
61
|
+
console_handler.setLevel(getattr(logging, log_level.upper()))
|
|
62
|
+
console_handler.setFormatter(formatter)
|
|
63
|
+
logger.addHandler(console_handler)
|
|
64
|
+
|
|
65
|
+
return logger
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# 全局日志记录器
|
|
69
|
+
logger = setup_logger()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_logger(name: str) -> logging.Logger:
|
|
73
|
+
"""获取指定名称的日志记录器"""
|
|
74
|
+
return setup_logger(name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# 便捷函数
|
|
78
|
+
def debug(msg: str, *args, **kwargs):
|
|
79
|
+
"""记录 DEBUG 日志"""
|
|
80
|
+
logger.debug(msg, *args, **kwargs)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def info(msg: str, *args, **kwargs):
|
|
84
|
+
"""记录 INFO 日志"""
|
|
85
|
+
logger.info(msg, *args, **kwargs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def warning(msg: str, *args, **kwargs):
|
|
89
|
+
"""记录 WARNING 日志"""
|
|
90
|
+
logger.warning(msg, *args, **kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def error(msg: str, *args, **kwargs):
|
|
94
|
+
"""记录 ERROR 日志"""
|
|
95
|
+
logger.error(msg, *args, **kwargs)
|