diff --git a/backend/bracket/models/metrics.py b/backend/bracket/models/metrics.py index c0361d6f8..4401a100a 100644 --- a/backend/bracket/models/metrics.py +++ b/backend/bracket/models/metrics.py @@ -8,6 +8,7 @@ from pydantic import BaseModel from bracket.utils.http import HTTPMethod +from bracket.utils.starlette import get_route_path from bracket.utils.types import EnumAutoStr if TYPE_CHECKING: @@ -28,7 +29,7 @@ class RequestDefinition(BaseModel): @staticmethod def from_request(request: Request) -> RequestDefinition: return RequestDefinition( - url=str(request.url.path), + url=get_route_path(request), method=HTTPMethod(request.method), ) diff --git a/backend/bracket/utils/starlette.py b/backend/bracket/utils/starlette.py new file mode 100644 index 000000000..b9d9a27c3 --- /dev/null +++ b/backend/bracket/utils/starlette.py @@ -0,0 +1,31 @@ +from starlette.requests import Request +from starlette.routing import Match, Route + + +def _get_route_for_request(request: Request) -> Route | None: + """ + Determine FastAPI route for a starlette Request + + Based on: https://stackoverflow.com/a/76203889 + """ + + route: Route | None = getattr(request.state, "__route_cached__", None) + if route is not None: + return route + + for route in request.app.routes: + match, _ = route.matches(request.scope) + if match is Match.FULL: + request.state.__route_cached__ = route + return route + + return None + + +def get_route_path(request: Request) -> str: + if (route := _get_route_for_request(request)) is not None: + msg = f"Expected `str` for `route.path`, got: {type(route.path).__name__}" + assert isinstance(route.path, str), msg + return route.path + + return f"unhandled:{request.url.path}"