sycommon-python-lib 0.1.6__tar.gz → 0.1.36__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.
Potentially problematic release.
This version of sycommon-python-lib might be problematic. Click here for more details.
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/PKG-INFO +10 -10
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/pyproject.toml +13 -10
- sycommon_python_lib-0.1.36/src/command/cli.py +167 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/database/database_service.py +6 -1
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/health/health_check.py +1 -1
- sycommon_python_lib-0.1.36/src/sycommon/health/metrics.py +13 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/logging/kafka_log.py +29 -12
- sycommon_python_lib-0.1.36/src/sycommon/logging/sql_logger.py +53 -0
- sycommon_python_lib-0.1.36/src/sycommon/middleware/cors.py +16 -0
- sycommon_python_lib-0.1.36/src/sycommon/middleware/docs.py +30 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/middleware.py +9 -1
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/traceid.py +33 -5
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/base_http.py +27 -25
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/mqlistener_config.py +1 -0
- sycommon_python_lib-0.1.36/src/sycommon/rabbitmq/rabbitmq_client.py +980 -0
- sycommon_python_lib-0.1.36/src/sycommon/rabbitmq/rabbitmq_pool.py +104 -0
- sycommon_python_lib-0.1.36/src/sycommon/rabbitmq/rabbitmq_service.py +821 -0
- sycommon_python_lib-0.1.36/src/sycommon/services.py +267 -0
- sycommon_python_lib-0.1.36/src/sycommon/sse/event.py +97 -0
- sycommon_python_lib-0.1.36/src/sycommon/sse/sse.py +278 -0
- sycommon_python_lib-0.1.36/src/sycommon/synacos/example.py +153 -0
- sycommon_python_lib-0.1.36/src/sycommon/synacos/example2.py +129 -0
- sycommon_python_lib-0.1.36/src/sycommon/synacos/feign.py +178 -0
- sycommon_python_lib-0.1.36/src/sycommon/synacos/feign_client.py +317 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/synacos/nacos_service.py +138 -57
- sycommon_python_lib-0.1.36/src/sycommon/synacos/param.py +75 -0
- sycommon_python_lib-0.1.36/src/sycommon/tools/__init__.py +0 -0
- sycommon_python_lib-0.1.36/src/sycommon/tools/docs.py +42 -0
- sycommon_python_lib-0.1.36/src/sycommon/tools/snowflake.py +33 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon_python_lib.egg-info/PKG-INFO +10 -10
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon_python_lib.egg-info/SOURCES.txt +14 -0
- sycommon_python_lib-0.1.36/src/sycommon_python_lib.egg-info/entry_points.txt +2 -0
- sycommon_python_lib-0.1.36/src/sycommon_python_lib.egg-info/requires.txt +15 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon_python_lib.egg-info/top_level.txt +1 -0
- sycommon_python_lib-0.1.6/src/sycommon/middleware/cors.py +0 -14
- sycommon_python_lib-0.1.6/src/sycommon/rabbitmq/rabbitmq_client.py +0 -721
- sycommon_python_lib-0.1.6/src/sycommon/rabbitmq/rabbitmq_service.py +0 -476
- sycommon_python_lib-0.1.6/src/sycommon/services.py +0 -217
- sycommon_python_lib-0.1.6/src/sycommon/synacos/feign.py +0 -311
- sycommon_python_lib-0.1.6/src/sycommon/tools/snowflake.py +0 -11
- sycommon_python_lib-0.1.6/src/sycommon_python_lib.egg-info/requires.txt +0 -15
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/README.md +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/setup.cfg +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.1.6/src/sycommon/synacos → sycommon_python_lib-0.1.36/src/sycommon/sse}/__init__.py +0 -0
- {sycommon_python_lib-0.1.6/src/sycommon/tools → sycommon_python_lib-0.1.36/src/sycommon/synacos}/__init__.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sycommon-python-lib
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.36
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: aio-pika>=9.5.7
|
|
8
|
-
Requires-Dist: aiohttp>=3.
|
|
8
|
+
Requires-Dist: aiohttp>=3.13.1
|
|
9
9
|
Requires-Dist: decorator>=5.2.1
|
|
10
|
-
Requires-Dist: fastapi>=0.
|
|
11
|
-
Requires-Dist: kafka>=1.3.5
|
|
10
|
+
Requires-Dist: fastapi>=0.120.0
|
|
12
11
|
Requires-Dist: kafka-python>=2.2.15
|
|
13
12
|
Requires-Dist: loguru>=0.7.3
|
|
14
|
-
Requires-Dist: mysql-connector-python>=9.
|
|
13
|
+
Requires-Dist: mysql-connector-python>=9.5.0
|
|
15
14
|
Requires-Dist: nacos-sdk-python>=2.0.9
|
|
16
|
-
Requires-Dist: pydantic>=2.
|
|
17
|
-
Requires-Dist: python-dotenv>=1.
|
|
18
|
-
Requires-Dist: pyyaml>=6.0.
|
|
19
|
-
Requires-Dist: sqlalchemy>=2.0.
|
|
15
|
+
Requires-Dist: pydantic>=2.12.3
|
|
16
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
17
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
18
|
+
Requires-Dist: sqlalchemy>=2.0.44
|
|
19
|
+
Requires-Dist: starlette>=0.48.0
|
|
20
20
|
Requires-Dist: uuid>=1.30
|
|
21
|
-
Requires-Dist: uvicorn>=0.
|
|
21
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
22
22
|
|
|
23
23
|
# sycommon-python-lib
|
|
24
24
|
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sycommon-python-lib"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.36"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"aio-pika>=9.5.7",
|
|
9
|
-
"aiohttp>=3.
|
|
9
|
+
"aiohttp>=3.13.1",
|
|
10
10
|
"decorator>=5.2.1",
|
|
11
|
-
"fastapi>=0.
|
|
12
|
-
"kafka>=1.3.5",
|
|
11
|
+
"fastapi>=0.120.0",
|
|
13
12
|
"kafka-python>=2.2.15",
|
|
14
13
|
"loguru>=0.7.3",
|
|
15
|
-
"mysql-connector-python>=9.
|
|
14
|
+
"mysql-connector-python>=9.5.0",
|
|
16
15
|
"nacos-sdk-python>=2.0.9",
|
|
17
|
-
"pydantic>=2.
|
|
18
|
-
"python-dotenv>=1.
|
|
19
|
-
"pyyaml>=6.0.
|
|
20
|
-
"sqlalchemy>=2.0.
|
|
16
|
+
"pydantic>=2.12.3",
|
|
17
|
+
"python-dotenv>=1.2.1",
|
|
18
|
+
"pyyaml>=6.0.3",
|
|
19
|
+
"sqlalchemy>=2.0.44",
|
|
20
|
+
"starlette>=0.48.0",
|
|
21
21
|
"uuid>=1.30",
|
|
22
|
-
"uvicorn>=0.
|
|
22
|
+
"uvicorn>=0.38.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
[tool.setuptools]
|
|
26
26
|
packages = {find = {where = ["src"]}}
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
sycommon = "command.cli:main"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import datetime
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from importlib.resources import files
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_all_files_in_directory(directory: Path) -> list[tuple[Path, str]]:
|
|
10
|
+
"""
|
|
11
|
+
获取目录下所有文件的相对路径(忽略__pycache__目录)
|
|
12
|
+
返回值: 元组列表 (模板文件路径, 相对目标路径)
|
|
13
|
+
"""
|
|
14
|
+
file_mappings = []
|
|
15
|
+
if not directory.exists() or not directory.is_dir():
|
|
16
|
+
return file_mappings
|
|
17
|
+
|
|
18
|
+
# 遍历目录下所有文件
|
|
19
|
+
for root, _, files in os.walk(directory):
|
|
20
|
+
# 跳过包含__pycache__的目录
|
|
21
|
+
if "__pycache__" in root:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
for file in files:
|
|
25
|
+
# 获取文件的绝对路径
|
|
26
|
+
file_path = Path(root) / file
|
|
27
|
+
# 计算相对模板目录的路径
|
|
28
|
+
rel_path = file_path.relative_to(directory)
|
|
29
|
+
# 添加到映射列表
|
|
30
|
+
file_mappings.append((file_path, str(rel_path)))
|
|
31
|
+
|
|
32
|
+
return file_mappings
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def init_project(project_name: str, project_type: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
初始化项目,自动读取模板文件并替换占位符
|
|
38
|
+
"""
|
|
39
|
+
project_path = Path(os.getcwd()) / project_name
|
|
40
|
+
if project_path.exists():
|
|
41
|
+
print(f"❌ 错误:工程 '{project_path}' 已存在")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
template_root = files("command.templates")
|
|
45
|
+
if not template_root.is_dir():
|
|
46
|
+
print("❌ 错误:未找到模板文件目录(command/templates)")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# 处理项目名称
|
|
50
|
+
short_project_name = project_name.replace("shengye-platform-", "")
|
|
51
|
+
short_project_name_upper = short_project_name.upper()
|
|
52
|
+
|
|
53
|
+
# 定义模板变量
|
|
54
|
+
context = {
|
|
55
|
+
"__cli__.project_name": project_name,
|
|
56
|
+
"__cli__.short_project_name": short_project_name,
|
|
57
|
+
"__cli__.short_project_name_upper": short_project_name_upper,
|
|
58
|
+
"__cli__.project_type": project_type,
|
|
59
|
+
"__cli__.create_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
60
|
+
"__cli__.author": os.getlogin(),
|
|
61
|
+
"__cli__.default_port": 8080
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# 自动获取基础模板文件和特定类型模板文件
|
|
65
|
+
base_dir = template_root / "base"
|
|
66
|
+
type_dir = template_root / project_type
|
|
67
|
+
|
|
68
|
+
base_files = get_all_files_in_directory(base_dir)
|
|
69
|
+
type_specific_files = get_all_files_in_directory(type_dir)
|
|
70
|
+
|
|
71
|
+
# 合并所有文件映射
|
|
72
|
+
file_mappings = base_files + type_specific_files
|
|
73
|
+
copied_files = 0
|
|
74
|
+
|
|
75
|
+
# 处理每个文件
|
|
76
|
+
for template_file, target_rel_path in file_mappings:
|
|
77
|
+
try:
|
|
78
|
+
# 1. 读取模板内容
|
|
79
|
+
template_content = template_file.read_text(encoding="utf-8")
|
|
80
|
+
|
|
81
|
+
# 2. 替换所有占位符(包含花括号)
|
|
82
|
+
rendered_content = template_content
|
|
83
|
+
for key, value in context.items():
|
|
84
|
+
# 精确匹配带有花括号的占位符
|
|
85
|
+
pattern = re.compile(rf'{{\s*{re.escape(key)}\s*}}')
|
|
86
|
+
rendered_content = pattern.sub(str(value), rendered_content)
|
|
87
|
+
|
|
88
|
+
# 3. 清理引号(针对YAML键值对格式)
|
|
89
|
+
rendered_content = re.sub(
|
|
90
|
+
r'(\w+)\s*:\s*["\']([^"\']+)["\']',
|
|
91
|
+
r'\1: \2',
|
|
92
|
+
rendered_content
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 4. 最后检查并移除任何残留的花括号
|
|
96
|
+
for value in context.values():
|
|
97
|
+
rendered_content = re.sub(
|
|
98
|
+
rf'{{+{re.escape(str(value))}+}}',
|
|
99
|
+
str(value),
|
|
100
|
+
rendered_content
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# 5. 处理文件后缀:直接移除.tpl后缀
|
|
104
|
+
if target_rel_path.endswith('.tpl'):
|
|
105
|
+
target_rel_path = target_rel_path[:-4]
|
|
106
|
+
|
|
107
|
+
# 6. 写入文件
|
|
108
|
+
target_file = project_path / target_rel_path
|
|
109
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
target_file.write_text(rendered_content, encoding="utf-8")
|
|
111
|
+
|
|
112
|
+
copied_files += 1
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"❌ 处理模板 {template_file} 失败: {str(e)}")
|
|
115
|
+
|
|
116
|
+
if copied_files > 0:
|
|
117
|
+
print(f"✅ 模板{project_type}工程 {project_name} 创建完成!")
|
|
118
|
+
print(f"📁 工程路径:{project_path}")
|
|
119
|
+
print(f"📊 共创建 {copied_files} 个文件")
|
|
120
|
+
else:
|
|
121
|
+
print(f"\n⚠️ 未创建任何文件,可能是缺少模板文件或模板路径配置错误")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main() -> None:
|
|
125
|
+
parser = argparse.ArgumentParser(
|
|
126
|
+
prog="sycommon",
|
|
127
|
+
description="sycommon 工具集 - 项目初始化工具",
|
|
128
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
129
|
+
)
|
|
130
|
+
subparsers = parser.add_subparsers(
|
|
131
|
+
dest="command", required=True, help="子命令(当前支持:init)"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
init_parser = subparsers.add_parser(
|
|
135
|
+
"init",
|
|
136
|
+
help="创建Web/Agent类型项目模板",
|
|
137
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
138
|
+
epilog="示例:\n"
|
|
139
|
+
" sycommon init web my_project # 创建Web类型项目\n"
|
|
140
|
+
" sycommon init agent my_project # 创建AI Agent类型项目"
|
|
141
|
+
)
|
|
142
|
+
init_parser.add_argument(
|
|
143
|
+
"project_type",
|
|
144
|
+
choices=["web", "agent"],
|
|
145
|
+
help="项目类型:web - Web服务项目;agent - AI Agent服务项目"
|
|
146
|
+
)
|
|
147
|
+
init_parser.add_argument(
|
|
148
|
+
"project_name",
|
|
149
|
+
help="工程名称(如 my_web_project,将创建同名根目录)"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
args = parser.parse_args()
|
|
154
|
+
if args.command == "init":
|
|
155
|
+
init_project(args.project_name, args.project_type)
|
|
156
|
+
except argparse.ArgumentError as e:
|
|
157
|
+
print(f"❌ 错误:{e}\n")
|
|
158
|
+
print(
|
|
159
|
+
f"请使用 {parser.prog} {args.command if 'args' in locals() else ''} -h 查看帮助")
|
|
160
|
+
except SystemExit:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
# uv pip install -e .
|
|
166
|
+
# sycommon init web my_project
|
|
167
|
+
main()
|
{sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/database/database_service.py
RENAMED
|
@@ -3,6 +3,7 @@ from sqlalchemy import create_engine, text
|
|
|
3
3
|
from sycommon.config.Config import SingletonMeta
|
|
4
4
|
from sycommon.config.DatabaseConfig import DatabaseConfig, convert_dict_keys
|
|
5
5
|
from sycommon.logging.kafka_log import SYLogger
|
|
6
|
+
from sycommon.logging.sql_logger import SQLTraceLogger
|
|
6
7
|
from sycommon.synacos.nacos_service import NacosService
|
|
7
8
|
|
|
8
9
|
|
|
@@ -62,9 +63,13 @@ class DatabaseConnector(metaclass=SingletonMeta):
|
|
|
62
63
|
max_overflow=20, # 最大溢出连接数
|
|
63
64
|
pool_timeout=30, # 连接超时时间(秒)
|
|
64
65
|
pool_recycle=3600, # 连接回收时间(秒)
|
|
65
|
-
pool_pre_ping=True # 每次获取连接前检查连接是否有效
|
|
66
|
+
pool_pre_ping=True, # 每次获取连接前检查连接是否有效
|
|
67
|
+
echo=False, # 打印 SQL 语句
|
|
66
68
|
)
|
|
67
69
|
|
|
70
|
+
# 注册 SQL 日志拦截器
|
|
71
|
+
SQLTraceLogger.setup_sql_logging(self.engine)
|
|
72
|
+
|
|
68
73
|
# 测试
|
|
69
74
|
if not self.test_connection():
|
|
70
75
|
raise Exception("Database connection test failed")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from fastapi import FastAPI, APIRouter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def setup_metrics_handler(app: FastAPI):
|
|
5
|
+
metrics_router = APIRouter()
|
|
6
|
+
|
|
7
|
+
@metrics_router.get("/metrics")
|
|
8
|
+
async def metrics():
|
|
9
|
+
return {"status": "alive"}
|
|
10
|
+
|
|
11
|
+
app.include_router(metrics_router)
|
|
12
|
+
|
|
13
|
+
return app
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import pprint
|
|
2
3
|
import sys
|
|
3
4
|
import traceback
|
|
4
5
|
import asyncio
|
|
@@ -70,6 +71,8 @@ class KafkaLogger(metaclass=SingletonMeta):
|
|
|
70
71
|
buffer_memory=67108864, # 增大缓冲区内存
|
|
71
72
|
connections_max_idle_ms=540000, # 连接最大空闲时间
|
|
72
73
|
reconnect_backoff_max_ms=10000, # 增加重连退避最大时间
|
|
74
|
+
max_in_flight_requests_per_connection=1, # 限制单个连接上未确认的请求数量
|
|
75
|
+
# enable_idempotence=True, # 开启幂等性
|
|
73
76
|
)
|
|
74
77
|
|
|
75
78
|
# 启动后台发送线程
|
|
@@ -170,7 +173,7 @@ class KafkaLogger(metaclass=SingletonMeta):
|
|
|
170
173
|
"className": "",
|
|
171
174
|
"sqlCost": 0,
|
|
172
175
|
"size": len(str(message)),
|
|
173
|
-
"uid": int(
|
|
176
|
+
"uid": int(Snowflake.next_id()) # 独立新的id
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
# 智能队列管理
|
|
@@ -467,12 +470,22 @@ class SYLogger:
|
|
|
467
470
|
thread_info = SYLogger._get_execution_context()
|
|
468
471
|
|
|
469
472
|
# 构建日志结构,添加线程/协程信息到threadName字段
|
|
470
|
-
request_log = {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
473
|
+
request_log = {}
|
|
474
|
+
if level == "ERROR":
|
|
475
|
+
request_log = {
|
|
476
|
+
"trace_id": str(trace_id) if trace_id else Snowflake.next_id(),
|
|
477
|
+
"message": msg_str,
|
|
478
|
+
"traceback": traceback.format_exc(),
|
|
479
|
+
"level": level,
|
|
480
|
+
"threadName": thread_info
|
|
481
|
+
}
|
|
482
|
+
else:
|
|
483
|
+
request_log = {
|
|
484
|
+
"trace_id": str(trace_id) if trace_id else Snowflake.next_id(),
|
|
485
|
+
"message": msg_str,
|
|
486
|
+
"level": level,
|
|
487
|
+
"threadName": thread_info
|
|
488
|
+
}
|
|
476
489
|
|
|
477
490
|
# 选择日志级别
|
|
478
491
|
_log = ''
|
|
@@ -487,22 +500,26 @@ class SYLogger:
|
|
|
487
500
|
logger.info(_log)
|
|
488
501
|
|
|
489
502
|
if os.getenv('DEV-LOG', 'false').lower() == 'true':
|
|
490
|
-
|
|
503
|
+
pprint.pprint(_log)
|
|
491
504
|
|
|
492
505
|
@staticmethod
|
|
493
|
-
def info(msg: any):
|
|
506
|
+
def info(msg: any, *args, **kwargs):
|
|
494
507
|
SYLogger._log(msg, "INFO")
|
|
495
508
|
|
|
496
509
|
@staticmethod
|
|
497
|
-
def warning(msg: any):
|
|
510
|
+
def warning(msg: any, *args, **kwargs):
|
|
498
511
|
SYLogger._log(msg, "WARNING")
|
|
499
512
|
|
|
500
513
|
@staticmethod
|
|
501
|
-
def
|
|
514
|
+
def debug(msg: any, *args, **kwargs):
|
|
515
|
+
SYLogger._log(msg, "DEBUG")
|
|
516
|
+
|
|
517
|
+
@staticmethod
|
|
518
|
+
def error(msg: any, *args, **kwargs):
|
|
502
519
|
SYLogger._log(msg, "ERROR")
|
|
503
520
|
|
|
504
521
|
@staticmethod
|
|
505
|
-
def exception(msg: any):
|
|
522
|
+
def exception(msg: any, *args, **kwargs):
|
|
506
523
|
"""记录异常信息,包括完整堆栈"""
|
|
507
524
|
trace_id = SYLogger.get_trace_id()
|
|
508
525
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from sqlalchemy import event
|
|
2
|
+
from sqlalchemy.engine import Engine
|
|
3
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SQLTraceLogger:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def setup_sql_logging(engine: Engine):
|
|
12
|
+
"""为 SQLAlchemy 引擎注册事件监听器"""
|
|
13
|
+
def serialize_params(params):
|
|
14
|
+
"""处理特殊类型参数的序列化"""
|
|
15
|
+
if isinstance(params, (list, tuple)):
|
|
16
|
+
return [serialize_params(p) for p in params]
|
|
17
|
+
elif isinstance(params, dict):
|
|
18
|
+
return {k: serialize_params(v) for k, v in params.items()}
|
|
19
|
+
elif isinstance(params, datetime):
|
|
20
|
+
return params.isoformat()
|
|
21
|
+
elif isinstance(params, Decimal):
|
|
22
|
+
return float(params)
|
|
23
|
+
else:
|
|
24
|
+
return params
|
|
25
|
+
|
|
26
|
+
@event.listens_for(Engine, "after_cursor_execute")
|
|
27
|
+
def after_cursor_execute(
|
|
28
|
+
conn, cursor, statement, parameters, context, executemany
|
|
29
|
+
):
|
|
30
|
+
try:
|
|
31
|
+
start_time = conn._execution_options.get(
|
|
32
|
+
"_start_time", time.time())
|
|
33
|
+
execution_time = (time.time() - start_time) * 1000
|
|
34
|
+
|
|
35
|
+
sql_log = {
|
|
36
|
+
"type": "SQL",
|
|
37
|
+
"statement": statement,
|
|
38
|
+
"parameters": serialize_params(parameters),
|
|
39
|
+
"execution_time_ms": round(execution_time, 2),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SYLogger.info(f"SQL执行: {sql_log}")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
SYLogger.error(f"SQL日志处理失败: {str(e)}")
|
|
45
|
+
|
|
46
|
+
@event.listens_for(Engine, "before_cursor_execute")
|
|
47
|
+
def before_cursor_execute(
|
|
48
|
+
conn, cursor, statement, parameters, context, executemany
|
|
49
|
+
):
|
|
50
|
+
try:
|
|
51
|
+
conn = conn.execution_options(_start_time=time.time())
|
|
52
|
+
except Exception as e:
|
|
53
|
+
SYLogger.error(f"SQL开始时间记录失败: {str(e)}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def setup_cors_handler(app):
|
|
5
|
+
# 允许所有源访问(*)
|
|
6
|
+
# 注意:此时allow_credentials必须为False,否则浏览器会拦截响应
|
|
7
|
+
app.add_middleware(
|
|
8
|
+
CORSMiddleware,
|
|
9
|
+
# allow_origins=["*"], # 允许所有源
|
|
10
|
+
# allow_credentials=False, # 必须为False(与*配合)
|
|
11
|
+
# allow_methods=["*"], # 允许所有HTTP方法
|
|
12
|
+
allow_headers=["*"], # 允许所有请求头
|
|
13
|
+
expose_headers=["*"] # 允许前端访问所有响应头
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return app
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from fastapi import FastAPI, APIRouter
|
|
2
|
+
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def setup_docs_handler(app: FastAPI):
|
|
6
|
+
docs_router = APIRouter()
|
|
7
|
+
|
|
8
|
+
@docs_router.get("/docs", include_in_schema=False)
|
|
9
|
+
async def custom_swagger_ui_html():
|
|
10
|
+
return get_swagger_ui_html(
|
|
11
|
+
openapi_url=app.openapi_url,
|
|
12
|
+
title=f"{app.title}",
|
|
13
|
+
swagger_favicon_url="https://static.sytechnology.com/img/sylogopng.png",
|
|
14
|
+
swagger_js_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui-bundle.js",
|
|
15
|
+
swagger_css_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui.css",
|
|
16
|
+
swagger_ui_parameters={"defaultModelsExpandDepth": -1},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@docs_router.get("/redoc", include_in_schema=False)
|
|
20
|
+
async def custom_redoc_html():
|
|
21
|
+
return get_redoc_html(
|
|
22
|
+
openapi_url=app.openapi_url,
|
|
23
|
+
title=f"{app.title}",
|
|
24
|
+
redoc_favicon_url="https://static.sytechnology.com/img/sylogopng.png",
|
|
25
|
+
redoc_js_url="https://cdn.bootcdn.net/ajax/libs/redoc/2.1.5/redoc.standalone.js",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app.include_router(docs_router)
|
|
29
|
+
|
|
30
|
+
return app
|
{sycommon_python_lib-0.1.6 → sycommon_python_lib-0.1.36}/src/sycommon/middleware/middleware.py
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from sycommon.health.metrics import setup_metrics_handler
|
|
1
2
|
from sycommon.health.ping import setup_ping_handler
|
|
2
3
|
from sycommon.middleware.cors import setup_cors_handler
|
|
4
|
+
from sycommon.middleware.docs import setup_docs_handler
|
|
3
5
|
from sycommon.middleware.exception import setup_exception_handler
|
|
4
6
|
from sycommon.middleware.monitor_memory import setup_monitor_memory_middleware
|
|
5
7
|
from sycommon.middleware.mq import setup_mq_middleware
|
|
@@ -25,7 +27,7 @@ class Middleware:
|
|
|
25
27
|
# app = setup_monitor_memory_middleware(app)
|
|
26
28
|
|
|
27
29
|
# 设置cors
|
|
28
|
-
|
|
30
|
+
app = setup_cors_handler(app)
|
|
29
31
|
|
|
30
32
|
# 健康检查
|
|
31
33
|
app = setup_health_handler(app)
|
|
@@ -33,7 +35,13 @@ class Middleware:
|
|
|
33
35
|
# ping
|
|
34
36
|
app = setup_ping_handler(app)
|
|
35
37
|
|
|
38
|
+
# metrics
|
|
39
|
+
app = setup_metrics_handler(app)
|
|
40
|
+
|
|
36
41
|
# 添加mq中间件
|
|
37
42
|
# app = setup_mq_middleware(app)
|
|
38
43
|
|
|
44
|
+
# doc
|
|
45
|
+
# app = setup_docs_handler(app)
|
|
46
|
+
|
|
39
47
|
return app
|
|
@@ -78,13 +78,19 @@ def setup_trace_id_handler(app):
|
|
|
78
78
|
response = await call_next(request)
|
|
79
79
|
|
|
80
80
|
content_type = response.headers.get("Content-Type", "")
|
|
81
|
+
|
|
82
|
+
# 处理 SSE 响应 - 关键修复点
|
|
81
83
|
if "text/event-stream" in content_type:
|
|
82
|
-
#
|
|
84
|
+
# 流式响应不能有Content-Length,移除它
|
|
85
|
+
if "Content-Length" in response.headers:
|
|
86
|
+
del response.headers["Content-Length"]
|
|
83
87
|
response.headers["x-traceId-header"] = trace_id
|
|
84
88
|
return response
|
|
85
89
|
|
|
90
|
+
# 处理普通响应
|
|
86
91
|
response_body = b""
|
|
87
92
|
try:
|
|
93
|
+
# 收集所有响应块
|
|
88
94
|
async for chunk in response.body_iterator:
|
|
89
95
|
response_body += chunk
|
|
90
96
|
|
|
@@ -95,17 +101,39 @@ def setup_trace_id_handler(app):
|
|
|
95
101
|
if "application/json" in content_type and not content_disposition.startswith("attachment"):
|
|
96
102
|
try:
|
|
97
103
|
data = json.loads(response_body)
|
|
98
|
-
data["
|
|
104
|
+
data["traceId"] = trace_id
|
|
99
105
|
new_body = json.dumps(
|
|
100
106
|
data, ensure_ascii=False).encode()
|
|
107
|
+
|
|
108
|
+
# 创建新响应,确保Content-Length正确
|
|
101
109
|
response = Response(
|
|
102
110
|
content=new_body,
|
|
103
111
|
status_code=response.status_code,
|
|
104
|
-
headers=dict(response.headers)
|
|
112
|
+
headers=dict(response.headers),
|
|
113
|
+
media_type=response.media_type
|
|
105
114
|
)
|
|
115
|
+
# 显式设置正确的Content-Length
|
|
106
116
|
response.headers["Content-Length"] = str(len(new_body))
|
|
107
117
|
except json.JSONDecodeError:
|
|
108
|
-
|
|
118
|
+
# 如果不是JSON,恢复原始响应体并更新长度
|
|
119
|
+
response = Response(
|
|
120
|
+
content=response_body,
|
|
121
|
+
status_code=response.status_code,
|
|
122
|
+
headers=dict(response.headers),
|
|
123
|
+
media_type=response.media_type
|
|
124
|
+
)
|
|
125
|
+
response.headers["Content-Length"] = str(
|
|
126
|
+
len(response_body))
|
|
127
|
+
else:
|
|
128
|
+
# 非JSON响应,恢复原始响应体
|
|
129
|
+
response = Response(
|
|
130
|
+
content=response_body,
|
|
131
|
+
status_code=response.status_code,
|
|
132
|
+
headers=dict(response.headers),
|
|
133
|
+
media_type=response.media_type
|
|
134
|
+
)
|
|
135
|
+
response.headers["Content-Length"] = str(
|
|
136
|
+
len(response_body))
|
|
109
137
|
except StopAsyncIteration:
|
|
110
138
|
pass
|
|
111
139
|
|
|
@@ -129,7 +157,7 @@ def setup_trace_id_handler(app):
|
|
|
129
157
|
"uploaded_files": files_info if files_info else None
|
|
130
158
|
}
|
|
131
159
|
error_message_str = json.dumps(error_message, ensure_ascii=False)
|
|
132
|
-
SYLogger.error(error_message_str)
|
|
160
|
+
SYLogger.error(error_message_str)
|
|
133
161
|
raise
|
|
134
162
|
finally:
|
|
135
163
|
# 清理上下文变量,防止泄漏
|