diff --git a/src/edge_proxy/middleware.py b/src/edge_proxy/middleware.py new file mode 100644 index 0000000..69974fc --- /dev/null +++ b/src/edge_proxy/middleware.py @@ -0,0 +1,41 @@ +import time +from typing import Any, Callable + +import structlog +from fastapi import Request +from starlette.middleware.base import BaseHTTPMiddleware + +logger = structlog.get_logger("edge_proxy.request") + + +class RequestLoggingMiddleware(BaseHTTPMiddleware): + """ + Log all incoming HTTP requests and responses with timing information. + """ + + async def dispatch( + self, request: Request, call_next: Callable[[Request], Any] + ) -> Any: + start_time = time.time() + + logger.debug( + "request", + method=request.method, + url=request.url.path, + client_host=request.client.host if request.client else None, + ) + + response = await call_next(request) + + duration = time.time() - start_time + + logger.debug( + "response", + method=request.method, + url=request.url.path, + status_code=response.status_code, + size=int(response.headers.get("content-length", 0)), + duration_ms=round(duration * 1000), + ) + + return response diff --git a/src/edge_proxy/server.py b/src/edge_proxy/server.py index 0d26b85..ca76860 100644 --- a/src/edge_proxy/server.py +++ b/src/edge_proxy/server.py @@ -14,6 +14,7 @@ from edge_proxy.environments import EnvironmentService from edge_proxy.exceptions import FeatureNotFoundError, FlagsmithUnknownKeyError from edge_proxy.logging import setup_logging +from edge_proxy.middleware import RequestLoggingMiddleware from edge_proxy.models import IdentityWithTraits from edge_proxy.settings import get_settings @@ -113,6 +114,7 @@ async def refresh_cache(): await environment_service.refresh_environment_caches() +app.add_middleware(RequestLoggingMiddleware) app.add_middleware( CORSMiddleware, allow_origins=settings.allow_origins, diff --git a/tests/test_middleware.py b/tests/test_middleware.py new file mode 100644 index 0000000..dad0061 --- /dev/null +++ b/tests/test_middleware.py @@ -0,0 +1,41 @@ +import json + +from fastapi import FastAPI +from fastapi.testclient import TestClient +from pytest_mock import MockerFixture + +from edge_proxy.middleware import RequestLoggingMiddleware + + +def test_request_logging_middleware(mocker: MockerFixture) -> None: + # Given + mock_logger = mocker.patch("edge_proxy.middleware.logger") + + app = FastAPI() + app.add_middleware(RequestLoggingMiddleware) + response = "ok" + + @app.get("/test") + async def test_endpoint(): + return response + + client = TestClient(app) + + # When + client.get("/test") + + # Then + mock_logger.debug.assert_any_call( + "request", + method="GET", + url="/test", + client_host="testclient", + ) + mock_logger.debug.assert_any_call( + "response", + method="GET", + url="/test", + status_code=200, + size=len(json.dumps(response)), + duration_ms=mocker.ANY, + )