Is there a way to obtain which route the request is taking in middleware? Right now, the only available thing is request.url.path, which doesn't give me a nice name for metrics.
I see in https://github.com/steinnes/timing-asgi/blob/master/timing_asgi/integrations/starlette.py that it basically just loops through all the routes to obtain the matching one. Is there a better way to do it?
Looping in that manner also runs into an issue where if there are multiple matching routes, like
/resource/default/1
/resource/1/1
it can't find the right one since they both match.
I got the same problem as @cypai with AuthenticationMiddleware. There is only request.scope["path"] and request.scope["raw_path"] available and I've to make quite some regex to handle the route properly.
Is it possible to build the Route() before *Middleware is called so one can get the route_name and other useful informations in the middleware? Maybe @cypai and I are both missing something?
So, the routing only occurs after the middleware handling.
I think options here are:
Thanks for your answer @tomchristie. I don't know Starlette enough to know which solution would be best. I know that in some other frameworks, there is usually some Events mechanisms with some Listeners attached to it.
If I can be of any help, let me know, we use Starlette in production in my company, so it's critical for us.
@tomchristie why the second option not something you're keen on ? (honest question, I have no opinion)
but yes I've run into this issue because I was trying to put in a ContextVars the route name, so that my logger can get this information
right now a workaround I've found (as I'm using fastapi and not startlette directly, so not sure how much it applies on startette) is subclassing like here https://fastapi.tiangolo.com/tutorial/custom-request-and-route/#custom-apiroute-class-in-a-router and there I can access the routing information I need before/after request
@allan-simon Can you share the code of your workaround?
sure thing
"""The goal is to have the route name of the current request
accessible anywhere by our loggers to help us contextualizing
when debugging though kibana or other centralized log visualizer
"""
from typing import Callable
from typing import Optional
from contextvars import ContextVar
from fastapi import APIRouter, FastAPI
from fastapi.routing import APIRoute
from starlette.requests import Request
from starlette.responses import Response
_route_name_ctx_var: ContextVar[Optional[str]] = ContextVar("route_name", default=None)
def get_route_name() -> Optional[str]:
return _route_name_ctx_var.get()
class LoggedRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
route_name = self.name
# we import here to avoid the circular dependencies
from app.logging import get_logger
async def custom_route_handler(request: Request) -> Response:
_route_name_ctx_var.set(route_name)
app_logger = get_logger()
app_logger.info({"step": "start"})
response: Response = await original_route_handler(request)
app_logger.info({"step": "end", "status_code": response.status_code})
return response
return custom_route_handler
and then I use it like this:
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.engine import Connection
from app.database import get_connection
from app.logged_route import LoggedRoute
router = APIRouter(route_class=LoggedRoute)
@allan-simon Thanks. What is get_route_name used for?
@shizidushu here I will not be able to simply copy paste you code, but here basically the usage:
I've configured all the loggers to have a JSON Formatter in order for my logs to be then integrated into a centralized log system (ELK stack)
in addition to this all the loggers have also a logfilter which add in the json , the field "route_name" (and other field , like user_id , request_id etc. ) so that if anywhere in the code I do "logger.info("hey") " I will be able to know it was called in the context of a given route
the goal is that this way it's easy to make statistics by api endpoint name
@allan-simon Thanks for your explanation. So, in your case, it is used to expose the route context ("route_name") to logger. It is an inspiring idea!
Question for the group here... tagging onto this thread because I think it may be related:
Am I correct in thinking route discovery is currently the best way to handle a pipeline of middleware for a given route or group of routes?
A little backdrop... I am thinking about the equivalent of a policies.js file in nodejs, where you may build a pipeline of checks for a given request:
admin_policies = [https_redirect, is_logged_in, is_admin]
user_policies = [https_redirect, is_logged_in]
My understanding is that policies (which are I think identical in implementation to middleware, though serve a subtly different purpose) are not directly supported in starlette, which is fine. That said, it might be nice to be able to assign functions (at the router level) assign those functions/routes to those policies so anyone who hits that route goes through that same pipeline.
I've found this kind of structure quite nice, and while starlette is meant to be lightweight and may expect something else to plug into it (which is fine), I'm curious if anyone has approached something similar... and if so, how?
EDIT: Please let me know if you think this question is more appropriate as its own issue.
Just wanted to add my voice to this issue. We want to log response times per endpoint. As this is done at the end of the request, it is perfectly fine for us to not know the endpoint name until the request has been resolved.
The workaround posted by @allan-simon might work, but I would rather handle response times in a middleware than in the routes themselves.
Most helpful comment
sure thing
and then I use it like this: