sycommon-python-lib 0.1.56b5__py3-none-any.whl → 0.1.57b4__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.
- sycommon/config/Config.py +24 -3
- sycommon/config/LangfuseConfig.py +15 -0
- sycommon/config/SentryConfig.py +13 -0
- sycommon/llm/embedding.py +269 -50
- sycommon/llm/get_llm.py +9 -218
- sycommon/llm/struct_token.py +192 -0
- sycommon/llm/sy_langfuse.py +103 -0
- sycommon/llm/usage_token.py +117 -0
- sycommon/logging/kafka_log.py +187 -433
- sycommon/middleware/exception.py +10 -16
- sycommon/middleware/timeout.py +2 -1
- sycommon/middleware/traceid.py +81 -76
- sycommon/notice/uvicorn_monitor.py +32 -27
- sycommon/rabbitmq/rabbitmq_client.py +247 -242
- sycommon/rabbitmq/rabbitmq_pool.py +201 -123
- sycommon/rabbitmq/rabbitmq_service.py +25 -843
- sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
- sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
- sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
- sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
- sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
- sycommon/sentry/__init__.py +0 -0
- sycommon/sentry/sy_sentry.py +35 -0
- sycommon/services.py +122 -96
- sycommon/synacos/nacos_client_base.py +121 -0
- sycommon/synacos/nacos_config_manager.py +107 -0
- sycommon/synacos/nacos_heartbeat_manager.py +144 -0
- sycommon/synacos/nacos_service.py +63 -783
- sycommon/synacos/nacos_service_discovery.py +157 -0
- sycommon/synacos/nacos_service_registration.py +270 -0
- sycommon/tools/env.py +62 -0
- sycommon/tools/merge_headers.py +20 -0
- sycommon/tools/snowflake.py +101 -153
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b4.dist-info}/METADATA +10 -8
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b4.dist-info}/RECORD +38 -20
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b4.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b4.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.56b5.dist-info → sycommon_python_lib-0.1.57b4.dist-info}/top_level.txt +0 -0
sycommon/middleware/exception.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from fastapi import Request, HTTPException
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
from pydantic import ValidationError
|
|
4
|
-
import
|
|
4
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def setup_exception_handler(app, config: dict):
|
|
@@ -15,7 +15,7 @@ def setup_exception_handler(app, config: dict):
|
|
|
15
15
|
int_MaxBytes = int(MaxBytes) / 1024 / 1024
|
|
16
16
|
return JSONResponse(
|
|
17
17
|
content={
|
|
18
|
-
'code': 413, 'error': f'File size exceeds the allowed limit of {int_MaxBytes}MB.'},
|
|
18
|
+
'code': 413, 'error': f'File size exceeds the allowed limit of {int_MaxBytes}MB.', 'traceId': SYLogger.get_trace_id()},
|
|
19
19
|
status_code=413
|
|
20
20
|
)
|
|
21
21
|
|
|
@@ -27,7 +27,8 @@ def setup_exception_handler(app, config: dict):
|
|
|
27
27
|
content={
|
|
28
28
|
"code": exc.status_code,
|
|
29
29
|
"message": exc.detail,
|
|
30
|
-
"path": str(request.url.path)
|
|
30
|
+
"path": str(request.url.path),
|
|
31
|
+
"traceId": SYLogger.get_trace_id()
|
|
31
32
|
}
|
|
32
33
|
)
|
|
33
34
|
|
|
@@ -39,7 +40,8 @@ def setup_exception_handler(app, config: dict):
|
|
|
39
40
|
content={
|
|
40
41
|
"code": 400,
|
|
41
42
|
"message": "参数验证失败",
|
|
42
|
-
"details": exc.errors()
|
|
43
|
+
"details": exc.errors(),
|
|
44
|
+
"traceId": SYLogger.get_trace_id()
|
|
43
45
|
}
|
|
44
46
|
)
|
|
45
47
|
|
|
@@ -55,30 +57,22 @@ def setup_exception_handler(app, config: dict):
|
|
|
55
57
|
status_code=exc.code,
|
|
56
58
|
content={
|
|
57
59
|
"code": exc.code,
|
|
58
|
-
"message": exc.message
|
|
60
|
+
"message": exc.message,
|
|
61
|
+
"traceId": SYLogger.get_trace_id()
|
|
59
62
|
}
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
# 5. 全局异常处理器(捕获所有未处理的异常)
|
|
63
66
|
@app.exception_handler(Exception)
|
|
64
67
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
65
|
-
# 记录详细错误信息
|
|
66
|
-
error_msg = f"请求路径: {request.url}\n"
|
|
67
|
-
error_msg += f"错误类型: {type(exc).__name__}\n"
|
|
68
|
-
error_msg += f"错误信息: {str(exc)}\n"
|
|
69
|
-
error_msg += f"堆栈信息: {traceback.format_exc()}"
|
|
70
|
-
|
|
71
|
-
# 使用你的日志服务记录错误
|
|
72
|
-
from sycommon.logging.kafka_log import SYLogger
|
|
73
|
-
SYLogger.error(error_msg)
|
|
74
|
-
|
|
75
68
|
# 返回统一格式的错误响应(生产环境可选择不返回详细信息)
|
|
76
69
|
return JSONResponse(
|
|
77
70
|
status_code=500,
|
|
78
71
|
content={
|
|
79
72
|
"code": 500,
|
|
80
73
|
"message": "服务器内部错误,请稍后重试",
|
|
81
|
-
"detail": str(exc) if config.get('DEBUG', False) else "Internal Server Error"
|
|
74
|
+
"detail": str(exc) if config.get('DEBUG', False) else "Internal Server Error",
|
|
75
|
+
"traceId": SYLogger.get_trace_id()
|
|
82
76
|
}
|
|
83
77
|
)
|
|
84
78
|
|
sycommon/middleware/timeout.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import time
|
|
3
3
|
from fastapi import Request
|
|
4
4
|
from fastapi.responses import JSONResponse
|
|
5
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def setup_request_timeout_middleware(app, config: dict):
|
|
@@ -14,6 +15,6 @@ def setup_request_timeout_middleware(app, config: dict):
|
|
|
14
15
|
response = await call_next(request)
|
|
15
16
|
duration = time.time() - request.state.start_time
|
|
16
17
|
if duration > REQUEST_TIMEOUT:
|
|
17
|
-
return JSONResponse(content={'code': 1, 'error': 'Request timed out'}, status_code=504)
|
|
18
|
+
return JSONResponse(content={'code': 1, 'error': 'Request timed out', 'traceId': SYLogger.get_trace_id()}, status_code=504)
|
|
18
19
|
return response
|
|
19
20
|
return app
|
sycommon/middleware/traceid.py
CHANGED
|
@@ -10,11 +10,8 @@ from sycommon.tools.snowflake import Snowflake
|
|
|
10
10
|
def setup_trace_id_handler(app):
|
|
11
11
|
@app.middleware("http")
|
|
12
12
|
async def trace_id_and_log_middleware(request: Request, call_next):
|
|
13
|
-
# ========== 1.
|
|
14
|
-
|
|
15
|
-
trace_id = request.headers.get(
|
|
16
|
-
"x-traceId-header") or request.headers.get("x-traceid-header")
|
|
17
|
-
# 无则生成雪花ID
|
|
13
|
+
# ========== 1. 请求阶段:获取/生成 TraceID ==========
|
|
14
|
+
trace_id = request.headers.get("x-traceid-header")
|
|
18
15
|
if not trace_id:
|
|
19
16
|
trace_id = Snowflake.id
|
|
20
17
|
|
|
@@ -37,38 +34,30 @@ def setup_trace_id_handler(app):
|
|
|
37
34
|
|
|
38
35
|
if is_json_content and request.method in ["POST", "PUT", "PATCH"]:
|
|
39
36
|
try:
|
|
40
|
-
# 兼容纯文本格式的 JSON
|
|
37
|
+
# 兼容纯文本格式的 JSON
|
|
41
38
|
if "text/plain" in content_type:
|
|
42
39
|
raw_text = await request.text(encoding="utf-8")
|
|
43
40
|
request_body = json.loads(raw_text)
|
|
44
41
|
else:
|
|
45
|
-
# application/json 直接解析
|
|
46
42
|
request_body = await request.json()
|
|
47
|
-
except Exception
|
|
43
|
+
except Exception:
|
|
48
44
|
try:
|
|
49
45
|
request_body = await request.json()
|
|
50
46
|
except Exception as e:
|
|
51
|
-
# 精准捕获 JSON 解析错误(而非泛 Exception)
|
|
52
47
|
request_body = {"error": f"JSON parse failed: {str(e)}"}
|
|
53
48
|
|
|
54
49
|
elif "multipart/form-data" in content_type and request.method in ["POST", "PUT"]:
|
|
55
50
|
try:
|
|
56
|
-
# 从请求头中提取boundary
|
|
57
51
|
boundary = None
|
|
58
52
|
if "boundary=" in content_type:
|
|
59
53
|
boundary = content_type.split("boundary=")[1].strip()
|
|
60
54
|
boundary = boundary.encode('ascii')
|
|
61
55
|
|
|
62
56
|
if boundary:
|
|
63
|
-
# 读取原始请求体
|
|
64
57
|
body = await request.body()
|
|
65
|
-
|
|
66
|
-
# 尝试从原始请求体中提取文件名
|
|
67
58
|
parts = body.split(boundary)
|
|
68
59
|
for part in parts:
|
|
69
60
|
part_str = part.decode('utf-8', errors='ignore')
|
|
70
|
-
|
|
71
|
-
# 使用正则表达式查找文件名
|
|
72
61
|
filename_match = re.search(
|
|
73
62
|
r'filename="([^"]+)"', part_str)
|
|
74
63
|
if filename_match:
|
|
@@ -82,62 +71,69 @@ def setup_trace_id_handler(app):
|
|
|
82
71
|
request_body = {
|
|
83
72
|
"error": f"Failed to process form data: {str(e)}"}
|
|
84
73
|
|
|
85
|
-
#
|
|
74
|
+
# 构建请求日志
|
|
86
75
|
request_message = {
|
|
87
|
-
"traceId": trace_id,
|
|
76
|
+
"traceId": trace_id,
|
|
88
77
|
"method": request.method,
|
|
89
78
|
"url": str(request.url),
|
|
90
79
|
"query_params": query_params,
|
|
91
80
|
"request_body": request_body,
|
|
92
81
|
"uploaded_files": files_info if files_info else None
|
|
93
82
|
}
|
|
94
|
-
|
|
95
|
-
|
|
83
|
+
SYLogger.info(json.dumps(request_message, ensure_ascii=False))
|
|
84
|
+
|
|
85
|
+
# 标记位:默认认为会发生异常
|
|
86
|
+
# 这样如果中途代码报错跳转到 except,finally 就不会 reset,保留 trace_id 给 Exception Handler
|
|
87
|
+
had_exception = True
|
|
96
88
|
|
|
97
89
|
try:
|
|
98
|
-
# 处理请求
|
|
90
|
+
# ========== 2. 处理请求 ==========
|
|
99
91
|
response = await call_next(request)
|
|
100
92
|
|
|
101
|
-
#
|
|
102
|
-
|
|
93
|
+
# ========== 3. 响应处理阶段 ==========
|
|
94
|
+
# 注意:此阶段发生的任何异常都会被下方的 except 捕获
|
|
95
|
+
# 从而保证 trace_id 不被清除,能够透传
|
|
96
|
+
|
|
97
|
+
response_content_type = response.headers.get(
|
|
98
|
+
"content-type", "").lower()
|
|
103
99
|
|
|
104
|
-
#
|
|
105
|
-
if "text/event-stream" in
|
|
100
|
+
# 处理 SSE (Server-Sent Events)
|
|
101
|
+
if "text/event-stream" in response_content_type:
|
|
106
102
|
try:
|
|
107
|
-
|
|
108
|
-
response.headers["x-traceId-header"] = trace_id
|
|
109
|
-
# 确保前端能读取(仅补充暴露头,不覆盖原有值)
|
|
103
|
+
response.headers["x-traceid-header"] = trace_id
|
|
110
104
|
expose_headers = response.headers.get(
|
|
111
105
|
"access-control-expose-headers", "")
|
|
112
106
|
if expose_headers:
|
|
113
|
-
if "x-
|
|
107
|
+
if "x-traceid-header" not in expose_headers.lower():
|
|
114
108
|
response.headers[
|
|
115
|
-
"access-control-expose-headers"] = f"{expose_headers}, x-
|
|
109
|
+
"access-control-expose-headers"] = f"{expose_headers}, x-traceid-header"
|
|
116
110
|
else:
|
|
117
|
-
response.headers["access-control-expose-headers"] = "x-
|
|
118
|
-
|
|
111
|
+
response.headers["access-control-expose-headers"] = "x-traceid-header"
|
|
112
|
+
|
|
113
|
+
# SSE 必须移除 Content-Length
|
|
119
114
|
headers_lower = {
|
|
120
115
|
k.lower(): k for k in response.headers.keys()}
|
|
121
116
|
if "content-length" in headers_lower:
|
|
122
117
|
del response.headers[headers_lower["content-length"]]
|
|
123
118
|
except AttributeError:
|
|
124
|
-
#
|
|
119
|
+
# 流式响应头只读处理
|
|
125
120
|
new_headers = dict(response.headers) if hasattr(
|
|
126
121
|
response.headers, 'items') else {}
|
|
127
|
-
new_headers["x-
|
|
128
|
-
# 保留原有暴露头,补充 traceId
|
|
122
|
+
new_headers["x-traceid-header"] = trace_id
|
|
129
123
|
if "access-control-expose-headers" in new_headers:
|
|
130
|
-
if "x-
|
|
131
|
-
new_headers["access-control-expose-headers"] += ", x-
|
|
124
|
+
if "x-traceid-header" not in new_headers["access-control-expose-headers"].lower():
|
|
125
|
+
new_headers["access-control-expose-headers"] += ", x-traceid-header"
|
|
132
126
|
else:
|
|
133
|
-
new_headers["access-control-expose-headers"] = "x-
|
|
134
|
-
# 移除 Content-Length
|
|
127
|
+
new_headers["access-control-expose-headers"] = "x-traceid-header"
|
|
135
128
|
new_headers.pop("content-length", None)
|
|
136
129
|
response.init_headers(new_headers)
|
|
130
|
+
|
|
131
|
+
# SSE 不处理 Body,直接返回
|
|
132
|
+
had_exception = False
|
|
137
133
|
return response
|
|
138
134
|
|
|
139
|
-
#
|
|
140
|
-
# 备份 CORS
|
|
135
|
+
# 处理非 SSE 响应
|
|
136
|
+
# 备份 CORS 头
|
|
141
137
|
cors_headers = {}
|
|
142
138
|
cors_header_keys = [
|
|
143
139
|
"access-control-allow-origin",
|
|
@@ -153,7 +149,7 @@ def setup_trace_id_handler(app):
|
|
|
153
149
|
cors_headers[key] = response.headers[k]
|
|
154
150
|
break
|
|
155
151
|
|
|
156
|
-
# 合并
|
|
152
|
+
# 合并 Headers
|
|
157
153
|
merged_headers = merge_headers(
|
|
158
154
|
source_headers=request.headers,
|
|
159
155
|
target_headers=response.headers,
|
|
@@ -161,19 +157,20 @@ def setup_trace_id_handler(app):
|
|
|
161
157
|
delete_keys={'content-length', 'accept', 'content-type'}
|
|
162
158
|
)
|
|
163
159
|
|
|
164
|
-
# 强制加入 x-
|
|
165
|
-
merged_headers["x-
|
|
166
|
-
# 恢复 CORS 头 + 补充 traceId 到暴露头
|
|
160
|
+
# 强制加入 x-traceid-header
|
|
161
|
+
merged_headers["x-traceid-header"] = trace_id
|
|
167
162
|
merged_headers.update(cors_headers)
|
|
163
|
+
|
|
164
|
+
# 更新暴露头
|
|
168
165
|
expose_headers = merged_headers.get(
|
|
169
166
|
"access-control-expose-headers", "")
|
|
170
167
|
if expose_headers:
|
|
171
|
-
if "x-
|
|
172
|
-
merged_headers["access-control-expose-headers"] = f"{expose_headers}, x-
|
|
168
|
+
if "x-traceid-header" not in expose_headers.lower():
|
|
169
|
+
merged_headers["access-control-expose-headers"] = f"{expose_headers}, x-traceid-header"
|
|
173
170
|
else:
|
|
174
|
-
merged_headers["access-control-expose-headers"] = "x-
|
|
171
|
+
merged_headers["access-control-expose-headers"] = "x-traceid-header"
|
|
175
172
|
|
|
176
|
-
#
|
|
173
|
+
# 应用 Headers
|
|
177
174
|
if hasattr(response.headers, 'clear'):
|
|
178
175
|
response.headers.clear()
|
|
179
176
|
for k, v in merged_headers.items():
|
|
@@ -187,27 +184,26 @@ def setup_trace_id_handler(app):
|
|
|
187
184
|
except (AttributeError, KeyError):
|
|
188
185
|
pass
|
|
189
186
|
|
|
190
|
-
#
|
|
187
|
+
# 处理响应体
|
|
191
188
|
response_body = b""
|
|
192
189
|
try:
|
|
193
190
|
async for chunk in response.body_iterator:
|
|
194
191
|
response_body += chunk
|
|
195
192
|
|
|
196
|
-
# 获取 Content-Disposition(统一小写)
|
|
197
193
|
content_disposition = response.headers.get(
|
|
198
194
|
"content-disposition", "").lower()
|
|
199
195
|
|
|
200
|
-
# JSON
|
|
201
|
-
if "application/json" in
|
|
196
|
+
# JSON 响应体注入 traceId
|
|
197
|
+
if "application/json" in response_content_type and not content_disposition.startswith("attachment"):
|
|
202
198
|
try:
|
|
203
199
|
data = json.loads(response_body)
|
|
204
200
|
new_body = response_body
|
|
205
|
-
if data:
|
|
206
|
-
data["traceId"] = trace_id
|
|
201
|
+
if isinstance(data, dict):
|
|
202
|
+
data["traceId"] = trace_id
|
|
207
203
|
new_body = json.dumps(
|
|
208
204
|
data, ensure_ascii=False).encode()
|
|
209
205
|
|
|
210
|
-
#
|
|
206
|
+
# 重建 Response 以更新 Body 和 Content-Length
|
|
211
207
|
response = Response(
|
|
212
208
|
content=new_body,
|
|
213
209
|
status_code=response.status_code,
|
|
@@ -215,12 +211,12 @@ def setup_trace_id_handler(app):
|
|
|
215
211
|
media_type=response.media_type
|
|
216
212
|
)
|
|
217
213
|
response.headers["content-length"] = str(len(new_body))
|
|
218
|
-
response.headers["x-
|
|
219
|
-
# 恢复 CORS
|
|
214
|
+
response.headers["x-traceid-header"] = trace_id
|
|
215
|
+
# 恢复 CORS
|
|
220
216
|
for k, v in cors_headers.items():
|
|
221
217
|
response.headers[k] = v
|
|
222
218
|
except json.JSONDecodeError:
|
|
223
|
-
# 非 JSON
|
|
219
|
+
# 非 JSON 或解析失败,仅更新长度
|
|
224
220
|
response = Response(
|
|
225
221
|
content=response_body,
|
|
226
222
|
status_code=response.status_code,
|
|
@@ -229,11 +225,11 @@ def setup_trace_id_handler(app):
|
|
|
229
225
|
)
|
|
230
226
|
response.headers["content-length"] = str(
|
|
231
227
|
len(response_body))
|
|
232
|
-
response.headers["x-
|
|
228
|
+
response.headers["x-traceid-header"] = trace_id
|
|
233
229
|
for k, v in cors_headers.items():
|
|
234
230
|
response.headers[k] = v
|
|
235
231
|
else:
|
|
236
|
-
# 非 JSON
|
|
232
|
+
# 非 JSON 响应
|
|
237
233
|
response = Response(
|
|
238
234
|
content=response_body,
|
|
239
235
|
status_code=response.status_code,
|
|
@@ -242,48 +238,57 @@ def setup_trace_id_handler(app):
|
|
|
242
238
|
)
|
|
243
239
|
response.headers["content-length"] = str(
|
|
244
240
|
len(response_body))
|
|
245
|
-
response.headers["x-
|
|
241
|
+
response.headers["x-traceid-header"] = trace_id
|
|
246
242
|
for k, v in cors_headers.items():
|
|
247
243
|
response.headers[k] = v
|
|
248
244
|
except StopAsyncIteration:
|
|
249
245
|
pass
|
|
250
246
|
|
|
251
|
-
#
|
|
247
|
+
# 构建响应日志
|
|
252
248
|
response_message = {
|
|
253
|
-
"traceId": trace_id,
|
|
249
|
+
"traceId": trace_id,
|
|
254
250
|
"status_code": response.status_code,
|
|
255
251
|
"response_body": response_body.decode('utf-8', errors='ignore'),
|
|
256
252
|
}
|
|
257
|
-
|
|
258
|
-
response_message, ensure_ascii=False)
|
|
259
|
-
SYLogger.info(response_message_str)
|
|
253
|
+
SYLogger.info(json.dumps(response_message, ensure_ascii=False))
|
|
260
254
|
|
|
261
|
-
#
|
|
255
|
+
# 兜底:确保 Header 必有 TraceId
|
|
262
256
|
try:
|
|
263
|
-
response.headers["x-
|
|
257
|
+
response.headers["x-traceid-header"] = trace_id
|
|
264
258
|
except AttributeError:
|
|
265
259
|
new_headers = dict(response.headers) if hasattr(
|
|
266
260
|
response.headers, 'items') else {}
|
|
267
|
-
new_headers["x-
|
|
261
|
+
new_headers["x-traceid-header"] = trace_id
|
|
268
262
|
if hasattr(response, "init_headers"):
|
|
269
263
|
response.init_headers(new_headers)
|
|
270
264
|
|
|
265
|
+
# 如果执行到这里,说明一切正常,标记为无异常
|
|
266
|
+
had_exception = False
|
|
271
267
|
return response
|
|
268
|
+
|
|
272
269
|
except Exception as e:
|
|
273
|
-
#
|
|
270
|
+
# ========== 4. 异常处理阶段 ==========
|
|
271
|
+
# 记录中间件层面的异常日志
|
|
274
272
|
error_message = {
|
|
275
273
|
"traceId": trace_id,
|
|
276
|
-
"error": str(e),
|
|
274
|
+
"error": f"Middleware Error: {str(e)}",
|
|
277
275
|
"query_params": query_params,
|
|
278
276
|
"request_body": request_body,
|
|
279
277
|
"uploaded_files": files_info if files_info else None
|
|
280
278
|
}
|
|
281
|
-
|
|
282
|
-
SYLogger.error(
|
|
279
|
+
# 使用 SYLogger.error,由于处于 except 块,会自动捕获堆栈
|
|
280
|
+
SYLogger.error(error_message)
|
|
281
|
+
|
|
282
|
+
# 关键:重新抛出异常,让 Global Exception Handler 接管
|
|
283
|
+
# 此时 had_exception 仍为 True,finally 不会 reset,trace_id 得以保留
|
|
283
284
|
raise
|
|
285
|
+
|
|
284
286
|
finally:
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
287
|
+
# ========== 5. 清理阶段 ==========
|
|
288
|
+
# 只有在没有任何异常的情况下(had_exception=False),才手动清除上下文
|
|
289
|
+
if not had_exception:
|
|
290
|
+
SYLogger.reset_trace_id(token)
|
|
291
|
+
SYLogger.reset_headers(header_token)
|
|
292
|
+
# 如果 had_exception 为 True,这里什么都不做,保留 ContextVar 供 Exception Handler 读取
|
|
288
293
|
|
|
289
294
|
return app
|
|
@@ -7,11 +7,12 @@ import json
|
|
|
7
7
|
from typing import Optional
|
|
8
8
|
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
|
9
9
|
from sycommon.config.Config import Config
|
|
10
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
async def send_wechat_markdown_msg(
|
|
13
14
|
content: str,
|
|
14
|
-
webhook: str =
|
|
15
|
+
webhook: str = None
|
|
15
16
|
) -> Optional[dict]:
|
|
16
17
|
"""
|
|
17
18
|
异步发送企业微信Markdown格式的WebHook消息
|
|
@@ -50,13 +51,14 @@ async def send_wechat_markdown_msg(
|
|
|
50
51
|
response_text) if response_text else {}
|
|
51
52
|
|
|
52
53
|
if status == 200 and response_data.get("errcode") == 0:
|
|
53
|
-
|
|
54
|
+
SYLogger.info(f"消息发送成功: {response_data}")
|
|
54
55
|
return response_data
|
|
55
56
|
else:
|
|
56
|
-
|
|
57
|
+
SYLogger.info(
|
|
58
|
+
f"消息发送失败 - 状态码: {status}, 响应: {response_data}")
|
|
57
59
|
return None
|
|
58
60
|
except Exception as e:
|
|
59
|
-
|
|
61
|
+
SYLogger.info(f"错误:未知异常 - {str(e)}")
|
|
60
62
|
return None
|
|
61
63
|
|
|
62
64
|
|
|
@@ -71,10 +73,12 @@ async def send_webhook(error_info: dict = None, webhook: str = None):
|
|
|
71
73
|
try:
|
|
72
74
|
service_name = Config().config.get('Name', "未知服务")
|
|
73
75
|
env = Config().config.get('Nacos', {}).get('namespaceId', '未知环境')
|
|
76
|
+
webHook = Config().config.get('llm', {}).get('WebHook', '未知环境')
|
|
74
77
|
except Exception as e:
|
|
75
78
|
service_name = "未知服务"
|
|
76
79
|
env = "未知环境"
|
|
77
|
-
|
|
80
|
+
webHook = None
|
|
81
|
+
SYLogger.info(f"读取配置失败: {str(e)}")
|
|
78
82
|
|
|
79
83
|
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
80
84
|
|
|
@@ -92,13 +96,14 @@ async def send_webhook(error_info: dict = None, webhook: str = None):
|
|
|
92
96
|
> 错误信息: <font color="danger">{error_msg}</font>
|
|
93
97
|
> 错误堆栈: {stack_trace}"""
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if webhook or webHook:
|
|
100
|
+
result = await send_wechat_markdown_msg(
|
|
101
|
+
content=markdown_content,
|
|
102
|
+
webhook=webhook or webHook
|
|
103
|
+
)
|
|
104
|
+
SYLogger.info(f"通知发送结果: {result}")
|
|
105
|
+
else:
|
|
106
|
+
SYLogger.info("未设置企业微信WebHook")
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
def run(*args, webhook: str = None, **kwargs):
|
|
@@ -133,7 +138,7 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
133
138
|
masked_query = urlencode(query, doseq=True)
|
|
134
139
|
masked_webhook = urlunparse(
|
|
135
140
|
(parsed.scheme, parsed.netloc, parsed.path, parsed.params, masked_query, parsed.fragment))
|
|
136
|
-
|
|
141
|
+
SYLogger.info(f"自定义企业微信WebHook: {masked_webhook}")
|
|
137
142
|
|
|
138
143
|
# 初始化错误信息
|
|
139
144
|
error_info = None
|
|
@@ -146,10 +151,10 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
146
151
|
except KeyboardInterrupt:
|
|
147
152
|
# 处理用户手动中断(不算启动失败)
|
|
148
153
|
elapsed = (datetime.now() - start_time).total_seconds()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
SYLogger.info(f"\n{'='*50}")
|
|
155
|
+
SYLogger.info(f"ℹ️ 应用被用户手动中断")
|
|
156
|
+
SYLogger.info(f"启动耗时: {elapsed:.2f} 秒")
|
|
157
|
+
SYLogger.info(f"{'='*50}\n")
|
|
153
158
|
sys.exit(0)
|
|
154
159
|
|
|
155
160
|
except Exception as e:
|
|
@@ -167,16 +172,16 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
# 打印错误信息
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
SYLogger.info(f"\n{'='*50}")
|
|
176
|
+
SYLogger.info(f"🚨 应用启动失败!")
|
|
177
|
+
SYLogger.info(f"失败时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
178
|
+
SYLogger.info(f"错误类型: {type(e).__name__}")
|
|
179
|
+
SYLogger.info(f"错误信息: {str(e)}")
|
|
180
|
+
SYLogger.info(f"\n📝 错误堆栈(关键):")
|
|
181
|
+
SYLogger.info(f"-"*50)
|
|
177
182
|
traceback.print_exc(file=sys.stdout)
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
SYLogger.info(f"\n⏱️ 启动耗时: {elapsed:.2f} 秒")
|
|
184
|
+
SYLogger.info(f"{'='*50}\n")
|
|
180
185
|
|
|
181
186
|
finally:
|
|
182
187
|
# 运行异步通知函数,传递自定义的webhook参数
|
|
@@ -186,7 +191,7 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
186
191
|
webhook=webhook
|
|
187
192
|
))
|
|
188
193
|
except Exception as e:
|
|
189
|
-
|
|
194
|
+
SYLogger.info(f"错误:异步通知失败 - {str(e)}")
|
|
190
195
|
# 启动失败时退出程序
|
|
191
196
|
sys.exit(1)
|
|
192
197
|
|