61 lines
2.0 KiB
Python
61 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
|
|
from app.core.logging import get_logger, set_request_id
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
"""请求日志中间件,记录请求追踪信息"""
|
|
|
|
async def dispatch(self, request: Request, call_next) -> Response:
|
|
# 设置请求追踪 ID(优先使用客户端传入的)
|
|
client_request_id = request.headers.get("X-Request-ID")
|
|
request_id = set_request_id(client_request_id)
|
|
|
|
start_time = time.time()
|
|
method = request.method
|
|
path = request.url.path
|
|
|
|
try:
|
|
response = await call_next(request)
|
|
duration_ms = (time.time() - start_time) * 1000
|
|
status_code = response.status_code
|
|
|
|
# 根据状态码选择日志级别
|
|
if status_code >= 500:
|
|
logger.error(
|
|
"%s %s -> %d (%.1fms)",
|
|
method, path, status_code, duration_ms
|
|
)
|
|
elif status_code >= 400:
|
|
logger.warning(
|
|
"%s %s -> %d (%.1fms)",
|
|
method, path, status_code, duration_ms
|
|
)
|
|
else:
|
|
# 跳过健康检查和静态资源的成功日志
|
|
if not (path == "/health" or path.startswith("/assets")):
|
|
logger.info(
|
|
"%s %s -> %d (%.1fms)",
|
|
method, path, status_code, duration_ms
|
|
)
|
|
|
|
# 在响应头中返回请求 ID
|
|
response.headers["X-Request-ID"] = request_id
|
|
return response
|
|
|
|
except Exception as e:
|
|
duration_ms = (time.time() - start_time) * 1000
|
|
logger.exception(
|
|
"%s %s -> 500 (%.1fms) error: %s",
|
|
method, path, duration_ms, str(e)
|
|
)
|
|
raise
|