from __future__ import annotations import logging import time from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import Response from app.db import get_pool from app.services.geo import geo_resolver from app.services.ws_hub import hub logger = logging.getLogger(__name__) class LoggingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next) -> Response: # Skip WebSocket upgrade requests on /ws/ paths if request.url.path.startswith("/ws/"): return await call_next(request) start = time.monotonic() ip = request.client.host if request.client else "unknown" response = await call_next(request) latency_ms = (time.monotonic() - start) * 1000.0 geo = geo_resolver.resolve(ip) try: pool = get_pool() row = await pool.fetchrow( """ INSERT INTO access_logs (ip, method, path, status_code, latency_ms, country, city, latitude, longitude, org) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at """, ip, request.method, request.url.path, response.status_code, latency_ms, geo.country, geo.city, geo.latitude, geo.longitude, geo.org, ) log_dict = { "id": row["id"], "ip": ip, "method": request.method, "path": request.url.path, "status_code": response.status_code, "latency_ms": latency_ms, "country": geo.country, "city": geo.city, "latitude": geo.latitude, "longitude": geo.longitude, "org": geo.org, "created_at": row["created_at"].isoformat(), } try: await hub.broadcast(log_dict) except Exception as exc: logger.error("WebSocket broadcast failed: %s", exc) except Exception as exc: logger.error("Failed to write access log: %s", exc) return response