1

I'm implementing a tokio/axum HTTP server. In the function where I run the server, I configure routing, add shared application services and add tracing layer.

My tracing configuration looks like this:

let tracing_layer = TraceLayer::new_for_http()
    .make_span_with(|_request: &Request<Body>| {
        let request_id = Uuid::new_v4().to_string();
        tracing::info_span!("http-request", %request_id)
    })
    .on_request(|request: &Request<Body>, _span: &Span| {
        tracing::info!("request: {} {}", request.method(), request.uri().path())
    })
    .on_response(
        |response: &Response<BoxBody>, latency: Duration, _span: &Span| {
            tracing::info!("response: {} {:?}", response.status(), latency)
        },
    )
    .on_failure(
        |error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {
            tracing::error!("error: {}", error)
        },
    );

let app = Router::new()
    // routes
    .layer(tracing_layer)
    // other layers
...

Trying to organize the code a bit I move the tracing layer configuration to a separate function. The trick is to provide a compiling return type for this function.

The first approach was to move the code as is and let an IDE generate the return type:

TraceLayer<SharedClassifier<ServerErrorsAsFailures>, fn(&Request<Body>) -> Span, fn(&Request<Body>, &Span), fn(&Response<BoxBody>, Duration, &Span), DefaultOnBodyChunk, DefaultOnEos, fn(ServerErrorsFailureClass, Duration, &Span)>

Which is completely unreadable, but the worst is it does not compile: "expected fn pointer, found closure"

In the second approach I changed fn into impl Fn that would mean a closure type. Again, I get an error that my closures are not Clone.

Third, I try to extract closures into separate functions. But then I get "expected fn pointer, found fn item".

What can I do 1) to make it compile and 2) to make it more readable?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
iTollu
  • 999
  • 13
  • 20

1 Answers1

5

Speaking from experience, breaking up the code like that is very hard due to all the generics. I would instead recommend functions that accept and return axum::Routers. That way you bypass all the generics:

fn add_middleware(router: Router) -> Router {
    router.layer(
        TraceLayer::new_for_http().make_span_with(...)
    )
}
David Pedersen
  • 399
  • 3
  • 10
  • Thanks you, David, this helps a lot! I would appreciate if you also give an example of how you router definition looks like with this approach. Also, the challenge to figure out the correct return type is still in force. – iTollu Feb 21 '22 at 15:42
  • 2
    `router = add_middleware(router);` – David Pedersen Feb 25 '22 at 15:33