423baff73b
- Docker bridge 网络隔离(8000 端口封死) - Gunicorn 4 Worker 多进程 - Alembic 数据库迁移基线 - 日志轮转 20m×3 - JWT 密钥 + DB 密码 + CORS 收紧 - 3-2-1 备份链路(NAS + R740-B 冷备) - 连接池 pool_pre_ping + pool_recycle=3600
60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
全局审计中间件
|
|
拦截所有入站 HTTP 请求,记录:方法、URL、客户端 IP、耗时、响应状态码。
|
|
日志输出到标准 logging,生产环境可对接 ELK / Loki 等日志收集系统。
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
|
|
# 配置审计专用 logger,与业务日志分离
|
|
audit_logger = logging.getLogger("audit")
|
|
audit_logger.setLevel(logging.INFO)
|
|
|
|
|
|
class AuditMiddleware(BaseHTTPMiddleware):
|
|
"""
|
|
审计中间件 - 记录每个请求的关键信息。
|
|
日志格式: [AUDIT] <客户端IP> <方法> <URL> <状态码> <耗时ms>
|
|
"""
|
|
|
|
async def dispatch(
|
|
self, request: Request, call_next: RequestResponseEndpoint
|
|
) -> Response:
|
|
# 提取客户端真实 IP (优先取反向代理传递的 X-Forwarded-For)
|
|
client_ip = request.headers.get(
|
|
"X-Forwarded-For", request.client.host if request.client else "unknown"
|
|
)
|
|
method = request.method
|
|
url = str(request.url)
|
|
|
|
start_time = time.perf_counter()
|
|
|
|
try:
|
|
response = await call_next(request)
|
|
except Exception:
|
|
# 未捕获异常也要记录审计日志
|
|
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
audit_logger.error(
|
|
"[AUDIT] %s %s %s 500 %.1fms (unhandled exception)",
|
|
client_ip, method, url, elapsed_ms,
|
|
)
|
|
raise
|
|
|
|
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
|
|
audit_logger.info(
|
|
"[AUDIT] %s %s %s %d %.1fms",
|
|
client_ip, method, url, response.status_code, elapsed_ms,
|
|
)
|
|
|
|
# 将审计信息注入响应头 (方便调试,生产环境可移除)
|
|
response.headers["X-Request-Duration-Ms"] = f"{elapsed_ms:.1f}"
|
|
|
|
return response
|