diff --git a/server/middleware.py b/server/middleware.py index 00329cc..ac809a0 100644 --- a/server/middleware.py +++ b/server/middleware.py @@ -55,6 +55,11 @@ _ip_request_counts: dict[str, list[float]] = defaultdict(list) # IP blocking config (set from main.py) BLOCKED_IPS: set[str] = set() +# Request stats (for summary logging) +_stats = {"blocked": 0, "rate_limited": 0, "total": 0} +_stats_last_log = time.time() +STATS_LOG_INTERVAL = 60 # Log stats every 60 seconds + # Suspicious paths that indicate bot scanning SUSPICIOUS_PATHS = { ".env", ".env.local", ".env.production", ".env.development", ".env.bak", @@ -140,15 +145,22 @@ def set_ip_config(blocked: Optional[set[str]] = None): class LoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): request_id = str(uuid.uuid4())[:8] + global _stats, _stats_last_log + client_ip = get_client_ip(request) - # Check if IP is blocked + # Check if IP is blocked (silent) if is_ip_blocked(client_ip): + _stats["blocked"] += 1 return Response(status_code=404, content="") # Check rate limit if not check_rate_limit(client_ip): - logger.warning(f"Rate limited: {client_ip} ({request.url.path})") + _stats["rate_limited"] += 1 + # Periodic stats logging instead of every warning + if time.time() - _stats_last_log > STATS_LOG_INTERVAL: + logger.warning(f"Stats: {_stats}") + _stats_last_log = time.time() return Response(status_code=429, content="Too many requests") # Check suspicious path (silent 404 for bots) @@ -160,6 +172,9 @@ class LoggingMiddleware(BaseHTTPMiddleware): # Skip logging for large file downloads (don't spam logs) is_file_download = path.startswith("/pack/") and "/file/" in path + # Track total requests for stats + _stats["total"] += 1 + # Log legitimate requests (except file downloads) start_time = time.time() @@ -173,6 +188,14 @@ class LoggingMiddleware(BaseHTTPMiddleware): if not is_file_download: logger.info(f"← {request.method} {path} → {response.status_code} ({duration:.0f}ms) [ID: {request_id}]") + # Periodic stats logging (only log if there were blocked/rate-limited) + now = time.time() + if now - _stats_last_log > STATS_LOG_INTERVAL: + if _stats["blocked"] > 0 or _stats["rate_limited"] > 0: + logger.warning(f"Blocked requests: IP_blocked={_stats['blocked']}, rate_limited={_stats['rate_limited']}") + _stats = {"blocked": 0, "rate_limited": 0, "total": 0} + _stats_last_log = now + response.headers["X-Request-ID"] = request_id return response