Skip to content

Commit fec6c72

Browse files
JiayuXuclaude
andcommitted
feat: 增强错误调试功能,完善traceback信息记录
- 完善异常处理器,添加详细的错误上下文和堆栈跟踪信息 - 新增调试助手工具,提供清晰的错误日志格式 - 增强日志上下文管理,支持请求级别的调试信息追踪 - 优化中间件异常处理,记录完整的请求生命周期 - 改进日志配置,新增关键错误日志文件 - 新增通用异常处理器,确保所有未捕获异常都被记录 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f73f949 commit fec6c72

6 files changed

Lines changed: 574 additions & 37 deletions

File tree

src/core/exceptions.py

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import json
2+
import traceback
13
from fastapi import HTTPException, Request
24
from fastapi.exceptions import RequestValidationError, ResponseValidationError
35
from fastapi.responses import JSONResponse
46
from starlette.responses import Response
57
from tortoise.exceptions import DoesNotExist, IntegrityError
68

9+
from log import logger
710
from settings.config import settings
811

912

@@ -12,6 +15,35 @@ class SettingNotFound(Exception):
1215

1316

1417
async def DoesNotExistHandle(req: Request, exc: DoesNotExist) -> JSONResponse:
18+
# 记录详细的错误信息到日志
19+
error_details = {
20+
"method": req.method,
21+
"url": str(req.url),
22+
"path": req.url.path,
23+
"query_params": dict(req.query_params),
24+
"client_ip": req.client.host if req.client else None,
25+
"user_agent": req.headers.get("user-agent"),
26+
"exception_type": type(exc).__name__,
27+
"exception_msg": str(exc),
28+
"traceback": traceback.format_exc()
29+
}
30+
31+
# 构建详细的错误信息
32+
error_message = f"DoesNotExist异常: {req.method} {req.url.path} - {exc}\n"
33+
error_message += f"Exception Type: {type(exc).__name__}\n"
34+
error_message += f"Exception Message: {str(exc)}\n"
35+
error_message += f"\nStack Trace:\n{error_details.get('traceback', 'No traceback available')}\n"
36+
error_message += f"\nRequest Context:\n"
37+
for key, value in error_details.items():
38+
if key != 'traceback':
39+
if isinstance(value, dict):
40+
error_message += f" {key}: {json.dumps(value, indent=2, ensure_ascii=False)}\n"
41+
else:
42+
error_message += f" {key}: {value}\n"
43+
error_message += "=" * 80
44+
45+
logger.error(error_message)
46+
1547
# 根据环境决定错误信息详细程度
1648
if settings.DEBUG:
1749
msg = f"Object not found: {exc}, query_params: {req.query_params}"
@@ -23,6 +55,30 @@ async def DoesNotExistHandle(req: Request, exc: DoesNotExist) -> JSONResponse:
2355

2456

2557
async def HttpExcHandle(request: Request, exc: HTTPException):
58+
# 记录HTTP异常详情
59+
error_details = {
60+
"method": request.method,
61+
"url": str(request.url),
62+
"path": request.url.path,
63+
"query_params": dict(request.query_params),
64+
"client_ip": request.client.host if request.client else None,
65+
"user_agent": request.headers.get("user-agent"),
66+
"status_code": exc.status_code,
67+
"exception_type": type(exc).__name__,
68+
"exception_msg": str(exc.detail),
69+
"traceback": traceback.format_exc()
70+
}
71+
72+
# 根据状态码决定日志级别
73+
if exc.status_code >= 500:
74+
logger.bind(**error_details).error(
75+
f"HTTP {exc.status_code}异常: {request.method} {request.url.path} - {exc.detail}"
76+
)
77+
elif exc.status_code >= 400:
78+
logger.bind(**error_details).warning(
79+
f"HTTP {exc.status_code}异常: {request.method} {request.url.path} - {exc.detail}"
80+
)
81+
2682
if exc.status_code == 401 and exc.headers and "WWW-Authenticate" in exc.headers:
2783
return Response(status_code=exc.status_code, headers=exc.headers)
2884
return JSONResponse(
@@ -32,6 +88,23 @@ async def HttpExcHandle(request: Request, exc: HTTPException):
3288

3389

3490
async def IntegrityHandle(request: Request, exc: IntegrityError):
91+
# 记录数据完整性错误详情
92+
error_details = {
93+
"method": request.method,
94+
"url": str(request.url),
95+
"path": request.url.path,
96+
"query_params": dict(request.query_params),
97+
"client_ip": request.client.host if request.client else None,
98+
"user_agent": request.headers.get("user-agent"),
99+
"exception_type": type(exc).__name__,
100+
"exception_msg": str(exc),
101+
"traceback": traceback.format_exc()
102+
}
103+
104+
logger.bind(**error_details).error(
105+
f"数据完整性错误: {request.method} {request.url.path} - {exc}"
106+
)
107+
35108
# 根据环境决定错误信息详细程度
36109
if settings.DEBUG:
37110
msg = f"IntegrityError: {exc}"
@@ -43,11 +116,29 @@ async def IntegrityHandle(request: Request, exc: IntegrityError):
43116

44117

45118
async def RequestValidationHandle(
46-
_: Request, exc: RequestValidationError
119+
request: Request, exc: RequestValidationError
47120
) -> JSONResponse:
121+
# 记录请求验证错误详情
122+
error_details = {
123+
"method": request.method,
124+
"url": str(request.url),
125+
"path": request.url.path,
126+
"query_params": dict(request.query_params),
127+
"client_ip": request.client.host if request.client else None,
128+
"user_agent": request.headers.get("user-agent"),
129+
"exception_type": type(exc).__name__,
130+
"exception_msg": str(exc),
131+
"validation_errors": exc.errors(),
132+
"traceback": traceback.format_exc()
133+
}
134+
135+
logger.bind(**error_details).warning(
136+
f"请求参数验证失败: {request.method} {request.url.path} - {len(exc.errors())}个错误"
137+
)
138+
48139
# 根据环境决定错误信息详细程度
49140
if settings.DEBUG:
50-
msg = f"RequestValidationError: {exc}"
141+
msg = f"RequestValidationError: {exc.errors()}"
51142
else:
52143
msg = "请求参数验证失败,请检查输入格式"
53144

@@ -56,13 +147,68 @@ async def RequestValidationHandle(
56147

57148

58149
async def ResponseValidationHandle(
59-
_: Request, exc: ResponseValidationError
150+
request: Request, exc: ResponseValidationError
60151
) -> JSONResponse:
152+
# 记录响应验证错误详情
153+
error_details = {
154+
"method": request.method,
155+
"url": str(request.url),
156+
"path": request.url.path,
157+
"query_params": dict(request.query_params),
158+
"client_ip": request.client.host if request.client else None,
159+
"user_agent": request.headers.get("user-agent"),
160+
"exception_type": type(exc).__name__,
161+
"exception_msg": str(exc),
162+
"validation_errors": exc.errors(),
163+
"traceback": traceback.format_exc()
164+
}
165+
166+
logger.bind(**error_details).error(
167+
f"响应格式验证错误: {request.method} {request.url.path} - {len(exc.errors())}个错误"
168+
)
169+
61170
# 根据环境决定错误信息详细程度
62171
if settings.DEBUG:
63-
msg = f"ResponseValidationError: {exc}"
172+
msg = f"ResponseValidationError: {exc.errors()}"
64173
else:
65174
msg = "服务器响应格式错误"
66175

67176
content = dict(code=500, msg=msg)
68177
return JSONResponse(content=content, status_code=500)
178+
179+
180+
async def UnhandledExceptionHandle(request: Request, exc: Exception) -> JSONResponse:
181+
"""处理所有未捕获的异常"""
182+
# 记录未处理异常的详细信息
183+
error_details = {
184+
"method": request.method,
185+
"url": str(request.url),
186+
"path": request.url.path,
187+
"query_params": dict(request.query_params),
188+
"client_ip": request.client.host if request.client else None,
189+
"user_agent": request.headers.get("user-agent"),
190+
"exception_type": type(exc).__name__,
191+
"exception_msg": str(exc),
192+
"exception_module": getattr(exc, "__module__", "unknown"),
193+
"traceback": traceback.format_exc()
194+
}
195+
196+
# 尝试获取请求体信息(如果可能)
197+
try:
198+
if hasattr(request, "_body"):
199+
error_details["request_body_size"] = len(request._body) if request._body else 0
200+
except Exception:
201+
pass
202+
203+
logger.bind(**error_details).critical(
204+
f"未处理的异常: {request.method} {request.url.path} - {type(exc).__name__}: {exc}"
205+
)
206+
207+
# 根据环境决定错误信息详细程度
208+
if settings.DEBUG:
209+
msg = f"Unhandled exception: {type(exc).__name__}: {exc}"
210+
else:
211+
msg = "服务器内部错误,请稍后重试"
212+
213+
content = dict(code=500, msg=msg)
214+
return JSONResponse(content=content, status_code=500)

src/core/init_app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
RequestValidationHandle,
2222
ResponseValidationError,
2323
ResponseValidationHandle,
24+
UnhandledExceptionHandle,
2425
)
2526
from core.middlewares import (
2627
BackGroundTaskMiddleware,
@@ -68,6 +69,8 @@ def register_exceptions(app: FastAPI):
6869
app.add_exception_handler(IntegrityError, IntegrityHandle)
6970
app.add_exception_handler(RequestValidationError, RequestValidationHandle)
7071
app.add_exception_handler(ResponseValidationError, ResponseValidationHandle)
72+
# 注册通用异常处理器(必须放在最后,作为兜底)
73+
app.add_exception_handler(Exception, UnhandledExceptionHandle)
7174
# 注册限流异常处理
7275
app.state.limiter = limiter
7376
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

src/core/middlewares.py

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from core.dependency import AuthControl
1515
from log import logger
16+
from log.context import LogContext
1617
from models.admin import AuditLog, User
1718

1819
from .bgtask import BgTasks
@@ -261,35 +262,45 @@ async def dispatch(
261262
) -> Response:
262263
"""处理请求并记录日志"""
263264
start_time = datetime.now()
265+
266+
# 设置请求级上下文信息
267+
request_id = LogContext.set_request_id()
268+
LogContext.update_context(
269+
method=request.method,
270+
path=request.url.path,
271+
url=str(request.url),
272+
query_params=dict(request.query_params),
273+
client_ip=request.client.host if request.client else None,
274+
user_agent=request.headers.get("user-agent"),
275+
content_type=request.headers.get("content-type"),
276+
content_length=request.headers.get("content-length"),
277+
start_time=start_time.isoformat(),
278+
)
279+
280+
# 获取带上下文的logger
281+
context_logger = LogContext.get_logger()
264282

265283
# 记录请求开始
266-
logger.info(
267-
f"请求开始: {request.method} {request.url.path}",
268-
extra={
269-
"method": request.method,
270-
"path": request.url.path,
271-
"query_params": dict(request.query_params),
272-
"client_ip": request.client.host if request.client else None,
273-
"user_agent": request.headers.get("user-agent"),
274-
},
275-
)
284+
context_logger.info(f"请求开始: {request.method} {request.url.path}")
276285

277286
try:
278287
response = await call_next(request)
279288

280289
# 计算处理时间
281290
end_time = datetime.now()
282291
process_time = (end_time - start_time).total_seconds() * 1000
292+
293+
# 更新上下文信息
294+
LogContext.update_context(
295+
status_code=response.status_code,
296+
process_time_ms=process_time,
297+
end_time=end_time.isoformat(),
298+
response_headers=dict(response.headers),
299+
)
283300

284301
# 记录请求完成
285-
logger.info(
286-
f"请求完成: {request.method} {request.url.path} - {response.status_code} ({process_time:.2f}ms)",
287-
extra={
288-
"method": request.method,
289-
"path": request.url.path,
290-
"status_code": response.status_code,
291-
"process_time_ms": process_time,
292-
},
302+
context_logger.info(
303+
f"请求完成: {request.method} {request.url.path} - {response.status_code} ({process_time:.2f}ms)"
293304
)
294305

295306
return response
@@ -298,16 +309,23 @@ async def dispatch(
298309
# 计算处理时间
299310
end_time = datetime.now()
300311
process_time = (end_time - start_time).total_seconds() * 1000
312+
313+
# 更新上下文信息
314+
LogContext.update_context(
315+
exception_occurred=True,
316+
exception_type=type(e).__name__,
317+
exception_msg=str(e),
318+
process_time_ms=process_time,
319+
end_time=end_time.isoformat(),
320+
traceback=traceback.format_exc()
321+
)
301322

302-
# 记录请求异常
303-
logger.error(
304-
f"请求异常: {request.method} {request.url.path} - {str(e)} ({process_time:.2f}ms)",
305-
extra={
306-
"method": request.method,
307-
"path": request.url.path,
308-
"error": str(e),
309-
"process_time_ms": process_time,
310-
},
323+
# 记录详细的请求异常信息
324+
context_logger.error(
325+
f"请求处理异常: {request.method} {request.url.path} - {type(e).__name__}: {str(e)} ({process_time:.2f}ms)"
311326
)
312327

313328
raise
329+
finally:
330+
# 清理请求上下文
331+
LogContext.clear()

0 commit comments

Comments
 (0)