enhanced_logging.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. """
  2. 增强的API日志中间件
  3. 记录所有接口的入参密文、入参明文、出参明文、出参密文
  4. 日志文件按天滚动,每天一个日志文件
  5. """
  6. import time
  7. import json
  8. from typing import Any, Callable, Dict, Optional
  9. from fastapi import Request
  10. from starlette.types import ASGIApp
  11. from app.utils.api_context import (
  12. RequestContext,
  13. set_request_context,
  14. get_decrypted_request,
  15. get_decrypted_response,
  16. get_encrypted_request,
  17. get_encrypted_response,
  18. )
  19. from app.utils.api_logger import api_logger
  20. class EnhancedAPILoggingMiddleware:
  21. """增强的API日志中间件"""
  22. def __init__(self, app: ASGIApp):
  23. self.app = app
  24. async def __call__(self, scope, receive, send):
  25. if scope.get("type") != "http":
  26. await self.app(scope, receive, send)
  27. return
  28. start_time = time.time()
  29. method = scope.get("method")
  30. server = scope.get("server") or ("localhost", 80)
  31. path = scope.get("raw_path", b"").decode("utf-8", errors="ignore")
  32. query_string = scope.get("query_string", b"").decode("utf-8", errors="ignore")
  33. scheme = scope.get("scheme", "http")
  34. url = f"{scheme}://{server[0]}:{server[1]}{path}" + (f"?{query_string}" if query_string else "")
  35. client = scope.get("client")
  36. client_ip = client[0] if client else None
  37. headers = {k.decode("utf-8"): v.decode("utf-8") for k, v in scope.get("headers", [])}
  38. user_agent = headers.get("user-agent", "")
  39. # 获取用户ID
  40. user_id = None
  41. try:
  42. header_user_id = headers.get("x-user-id") or headers.get("X-User-Id")
  43. if header_user_id:
  44. user_id = int(header_user_id)
  45. except (ValueError, TypeError):
  46. user_id = None
  47. # 收集请求体(仅在有 body 时)
  48. body_bytes = b""
  49. async def receive_wrapper():
  50. nonlocal body_bytes
  51. message = await receive()
  52. if message.get("type") == "http.request":
  53. body_bytes += message.get("body", b"")
  54. return message
  55. # 收集响应状态与 body 分片
  56. status_code = 500
  57. resp_chunks = []
  58. async def send_wrapper(message):
  59. nonlocal status_code, resp_chunks
  60. if message.get("type") == "http.response.start":
  61. status_code = message.get("status", 0)
  62. if message.get("type") == "http.response.body":
  63. chunk = message.get("body", b"")
  64. if chunk:
  65. resp_chunks.append(chunk)
  66. await send(message)
  67. # 加密的请求数据
  68. encrypted_request = None
  69. if body_bytes:
  70. try:
  71. encrypted_request = body_bytes.decode("utf-8", errors="ignore")
  72. except Exception:
  73. encrypted_request = str(body_bytes)
  74. # 创建请求上下文
  75. ctx = RequestContext(
  76. encrypted_request=encrypted_request,
  77. encrypted_response=None,
  78. )
  79. error_message = None
  80. try:
  81. with ctx:
  82. # 设置请求上下文
  83. set_request_context({
  84. "encrypted_request": encrypted_request,
  85. "encrypted_response": None,
  86. "decrypted_request": None,
  87. "decrypted_response": None,
  88. })
  89. # 调用下游应用
  90. await self.app(scope, receive_wrapper, send_wrapper)
  91. except Exception as e:
  92. error_message = str(e)
  93. raise
  94. finally:
  95. try:
  96. # 收集响应数据
  97. response_body_bytes = b"".join(resp_chunks) if resp_chunks else b""
  98. encrypted_response = None
  99. if response_body_bytes:
  100. try:
  101. encrypted_response = response_body_bytes.decode("utf-8", errors="ignore")
  102. except Exception:
  103. encrypted_response = str(response_body_bytes)
  104. # 计算执行时间
  105. processing_time_ms = int((time.time() - start_time) * 1000)
  106. # 获取解密后的请求和响应数据
  107. decrypted_request = get_decrypted_request()
  108. decrypted_response = get_decrypted_response()
  109. # 记录完整的API调用日志
  110. api_logger.log_api_call(
  111. endpoint=url,
  112. method=method,
  113. encrypted_request=encrypted_request,
  114. decrypted_request=decrypted_request,
  115. encrypted_response=encrypted_response,
  116. decrypted_response=decrypted_response,
  117. processing_time_ms=processing_time_ms,
  118. status_code=status_code,
  119. user_id=user_id,
  120. client_ip=client_ip,
  121. error_message=error_message,
  122. )
  123. except Exception as e:
  124. # 日志记录失败不应影响响应
  125. print(f"记录API日志失败: {e}")
  126. def setup_enhanced_api_logging(app: Callable) -> None:
  127. """
  128. 为FastAPI应用设置增强的API日志中间件
  129. 在应用创建后调用此函数来注册中间件
  130. """
  131. from fastapi import FastAPI
  132. if isinstance(app, FastAPI):
  133. app.add_middleware(EnhancedAPILoggingMiddleware)