Search code examples
rustrust-tower

How to configure tower_http TraceLayer in a separate function?


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?


Solution

  • 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(...)
        )
    }