logging.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. """
  2. API日志中间件 - 管理端专用
  3. """
  4. import time
  5. from app.database import SessionLocal
  6. from app.models.api_log import APILog
  7. from app.core.logger import logger, get_logger_by_path
  8. from app.utils.timezone import beijing_now
  9. class APILoggingASGIMiddleware:
  10. """更可靠的 ASGI 层日志中间件,捕获请求/响应流(用于记录完整返回报文)"""
  11. def __init__(self, app):
  12. self.app = app
  13. async def __call__(self, scope, receive, send):
  14. if scope.get("type") != "http":
  15. await self.app(scope, receive, send)
  16. return
  17. start_time = time.time()
  18. method = scope.get("method")
  19. server = scope.get("server") or ("localhost", 80)
  20. path = scope.get("raw_path", b"").decode("utf-8", errors="ignore")
  21. query_string = scope.get("query_string", b"").decode("utf-8", errors="ignore")
  22. scheme = scope.get("scheme", "http")
  23. url = f"{scheme}://{server[0]}:{server[1]}{path}" + (f"?{query_string}" if query_string else "")
  24. client = scope.get("client")
  25. client_ip = client[0] if client else None
  26. headers = {k.decode("utf-8"): v.decode("utf-8") for k, v in scope.get("headers", [])}
  27. user_agent = headers.get("user-agent", "")
  28. # 直接从请求头获取user_id和user_name,进行组合校验
  29. user_id = None
  30. admin_id = None
  31. try:
  32. # 从header获取user_id和user_name
  33. header_user_id = headers.get("x-user-id") or headers.get("X-User-Id")
  34. header_user_name = headers.get("x-user-name") or headers.get("X-User-Name")
  35. if header_user_id and header_user_name:
  36. # 管理端:从Admin表查询
  37. db_temp = SessionLocal()
  38. try:
  39. from app.models.admin import Admin
  40. admin = db_temp.query(Admin).filter(
  41. Admin.id == int(header_user_id),
  42. Admin.username == header_user_name
  43. ).first()
  44. if admin:
  45. admin_id = admin.id
  46. finally:
  47. db_temp.close()
  48. except Exception:
  49. # 如果header验证失败,记录为None
  50. user_id = None
  51. admin_id = None
  52. # 包装 receive,收集请求体(仅在有 body 时)
  53. body_bytes = b""
  54. async def receive_wrapper():
  55. nonlocal body_bytes
  56. message = await receive()
  57. if message.get("type") == "http.request":
  58. body_bytes += message.get("body", b"")
  59. return message
  60. # 包装 send,收集响应状态与 body 分片
  61. status_code = 500
  62. resp_chunks = []
  63. async def send_wrapper(message):
  64. nonlocal status_code, resp_chunks
  65. if message.get("type") == "http.response.start":
  66. status_code = message.get("status", 0)
  67. if message.get("type") == "http.response.body":
  68. chunk = message.get("body", b"")
  69. if chunk:
  70. resp_chunks.append(chunk)
  71. await send(message)
  72. # 调用下游应用并在返回后记录日志
  73. try:
  74. await self.app(scope, receive_wrapper, send_wrapper)
  75. finally:
  76. try:
  77. response_body_bytes = b"".join(resp_chunks) if resp_chunks else b""
  78. try:
  79. response_text = response_body_bytes.decode("utf-8", errors="ignore") if response_body_bytes else None
  80. except Exception:
  81. response_text = str(response_body_bytes)
  82. if not response_text:
  83. response_text = f"Status: {status_code}"
  84. if len(response_text) > 2000:
  85. response_text = response_text[:2000] + "..."
  86. request_text = None
  87. if body_bytes:
  88. try:
  89. request_text = body_bytes.decode("utf-8", errors="ignore")
  90. if len(request_text) > 2000:
  91. request_text = request_text[:2000] + "..."
  92. except Exception:
  93. request_text = str(body_bytes)
  94. # 计算执行时间
  95. execution_time = int((time.time() - start_time) * 1000)
  96. # 根据路径选择对应的logger
  97. api_logger = get_logger_by_path(path)
  98. # 在控制台打印API入参出参
  99. try:
  100. request_display = request_text[:1000] + ('...' if request_text and len(request_text) > 1000 else '') if request_text else 'None'
  101. response_display = response_text[:1000] + ('...' if response_text and len(response_text) > 1000 else '') if response_text else 'None'
  102. api_logger.info(f"[API请求] {method} {url} - 请求数据: {request_display}")
  103. api_logger.info(f"[API响应] {method} {url} - 响应数据: {response_display} - 执行时间: {execution_time}ms")
  104. except Exception as e:
  105. api_logger.warning(f"[API日志] 控制台日志输出失败: {e}")
  106. # 写入数据库(异步性与错误容忍)
  107. try:
  108. db = SessionLocal()
  109. try:
  110. # 临时调试日志:打印请求头中的 X-User-Id/X-User-Name 与 scope 注入的 verified ids
  111. try:
  112. headers_lower = {k.lower(): v for k, v in headers.items()} if 'headers' in locals() else {}
  113. xu = headers_lower.get('x-user-id') or headers_lower.get('x-user-name') or None
  114. except Exception:
  115. xu = None
  116. api_logger.debug(f"[APILoggingASGI][DEBUG] endpoint={url} verified_user_id={scope.get('verified_user_id')} verified_admin_id={scope.get('verified_admin_id')} header_X_User_Id={xu}")
  117. api_log = APILog(
  118. user_id=user_id,
  119. admin_id=scope.get("verified_admin_id") if scope.get("verified_admin_id") else None,
  120. endpoint=url,
  121. method=method,
  122. request_data=request_text if request_text else None,
  123. response_data=response_text if response_text else None,
  124. ip_address=client_ip,
  125. user_agent=user_agent,
  126. response_code=status_code,
  127. execution_time=execution_time,
  128. created_at=beijing_now()
  129. )
  130. db.add(api_log)
  131. db.commit()
  132. api_logger.debug(f"[APILoggingASGI] 成功记录API日志: {url} {method} {status_code} {execution_time}ms")
  133. except Exception as e:
  134. db.rollback()
  135. api_logger.error(f"[APILoggingASGI] 记录API日志失败: {url} {method} {status_code}", exc_info=True)
  136. finally:
  137. db.close()
  138. except Exception as e:
  139. api_logger.error(f"[APILoggingASGI] 创建数据库连接失败: {e}")
  140. except Exception:
  141. pass