salvo-rs/salvo

Get matched route information

cortopy opened this issue · 1 comments

Is your feature request related to a problem? Please describe.

When a Request is processed, I need to log which route is being handled. The Opentelemetry definition of route is very handy:

The matched route, that is, the path template in the format used by the respective server framework.

And they give examples like: /users/:userID?, {controller}/{action}/{id?}

I need this because:

  • When searching log records, I don't want to search for /user/123 but /user/<id> since I may want to see requests for that route.
  • Some observability providers like Grafana cloud charge per cardinality, and logging requests with variable parameters can be very expensive.
  • Due to privacy legistlation like GDPR, identifiable information like user ids need to be removed or masked. Having a way to know if the Request has been matched with a route that has parameters would be great.
  • Opentelemetry defines http.route as an established convention to log which server route a given request has been matched with.

Describe the solution you'd like
It'd be amazing if Request or Response had something like:

struct RouteInformation {
   path: &str
}

impl RouteInformation {
  pub fn masked_uri() -> String {
     // Some logic to replace parameters with something lkike ****
  }
}

struct Request {
   route: RouteInformation,
   ...
}
#[async_trait]
impl Handler for Logger {
    async fn handle(
        &self,
        req: &mut Request,
        depot: &mut Depot,
        res: &mut Response,
        ctrl: &mut FlowCtrl,
    ) {
        let span = info_span!(
            "Request",
            // Instead of: path = %req.uri(),
            url.path = %req.route.masked(),
            http.route = %req.route.path(),
        );

        async move {
            let now = Instant::now();
            ctrl.call_next(req, depot, res).await;
            let duration = now.elapsed();

            let status = res.status_code.unwrap_or(match &res.body {
                ResBody::None => StatusCode::NOT_FOUND,
                ResBody::Error(e) => e.code,
                _ => StatusCode::OK,
            });
            info!(
                http.response.status_code = %status,
                http.duration = ?duration,
                "Response"
            );
        }
        .instrument(span)
        .await
    }
}

Describe alternatives you've considered
Request doesn't provide information about routes so it would be some laborious manual process for each known route. This would be prone to error.

Any other web framework has this feature?