| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- """请求日志中间件 — 记录最近 MAX_ENTRIES 条请求到内存"""
- import time
- import asyncio
- from collections import deque
- from typing import Optional
- from fastapi import Request, Response
- import logging
- logger = logging.getLogger(__name__)
- MAX_ENTRIES = 500
- _request_log: deque = deque(maxlen=MAX_ENTRIES)
- def get_request_log() -> list:
- """返回请求日志副本(最近在前)"""
- return list(_request_log)[::-1]
- def clear_request_log() -> int:
- """清空日志,返回清空条数"""
- n = len(_request_log)
- _request_log.clear()
- return n
- async def request_log_middleware(request: Request, call_next) -> Response:
- start_time = time.perf_counter()
- client_ip = request.client.host if request.client else "-"
-
- # 读取请求体(不重复消费)
- body = None
- if request.method in ("POST", "PUT", "PATCH"):
- try:
- raw = await request.body()
- if raw:
- try:
- body = raw.decode("utf-8")
- if len(body) > 2000:
- body = body[:2000] + "...(truncated)"
- except Exception:
- body = f"<binary {len(raw)} bytes>"
- except Exception:
- pass
-
- try:
- response = await call_next(request)
- except Exception as exc:
- elapsed = time.perf_counter() - start_time
- status_code = 500
- response_body = ""
- logger.error("REQUEST EXCEPTION: %s %s %.2fs error=%r",
- request.method, request.url.path, elapsed, exc)
- _request_log.appendleft({
- "ts": time.strftime("%H:%M:%S"),
- "method": request.method,
- "path": request.url.path,
- "status": 500,
- "duration_ms": round(elapsed * 1000),
- "client_ip": client_ip,
- "body": body,
- "error": str(exc)[:500],
- })
- # 构造错误响应
- from fastapi.responses import JSONResponse
- return JSONResponse(status_code=500, content={"detail": str(exc)})
-
- elapsed = time.perf_counter() - start_time
- status_code = response.status_code
-
- # 读取响应体(不可逆,只取内容长度)
- response_body = ""
- if hasattr(response, "body") and response.body:
- try:
- response_body = response.body.decode("utf-8")[:1000]
- except Exception:
- response_body = f"<binary {len(response.body)} bytes>"
-
- # 写入日志
- entry = {
- "ts": time.strftime("%H:%M:%S"),
- "method": request.method,
- "path": request.url.path,
- "status": status_code,
- "duration_ms": round(elapsed * 1000),
- "client_ip": client_ip,
- "body": body,
- "response": response_body,
- }
- _request_log.appendleft(entry)
-
- # 超5秒标记 warning
- if elapsed > 5:
- logger.warning("SLOW_REQUEST: %s %s %.2fs", request.method, request.url.path, elapsed)
- else:
- logger.debug("REQUEST: %s %s %.2fs", request.method, request.url.path, elapsed)
-
- response.headers["X-Response-Time"] = f"{elapsed:.3f}s"
- return response
|