How to check response.status_code in the on_failure callback of `trace` crate?

I want to check if the response returned from the the handler function is a client error. If it is, I want to log a warning and thereby marking the trace as error free.

This is how I am currently doing it:

let trace_layer = TraceLayer::new_for_http().on_response(
        |response: &Response<_>, _: Duration, _: &tracing::Span| {
            if response.status().is_client_error() {
                warn!("ControlPlane.http_request_trace warning: {}", response.status());
            }
        },
    ).make_span_with(|request: &Request<_>| {
        tracing::info_span!("ControlPlane.http_request_trace", method = %request.method(), uri = %request.uri())
    });

However, I don’t think it is optimal to check each response and instead, I should check whether the response was a client side error only on failures.

From the official documentation of tower_http crate, on_failure can be used here, it seems:

The on_failure callback is called when:
The inner Service’s response future resolves to an error.
A response is classified as a failure.
Body::poll_frame returns an error.
An end-of-stream is classified as a failure.

I want to understand how can I check the response’s status code if I were to use on_failure such that I don’t have to check the response each time.

A TraceLayer classifies responses as successful or failed by a ClassifyResponse implementation set upon creation. Creating the layer with new_for_http uses ServerErrorsAsFailures, but if you want different behavior then you’ll need a different implementation supplied using new instead.

There is already a StatusInRangeAsFailures that can do what you want so you won’t need anything too custom, and even the example for it is to consider 500 and 400 level statuses as “failures”. Then you can use on_failure to trace the failed response, which now covers client-side errors as well. Using it would look like this:

use tower_http::classify::{SharedClassifier, StatusInRangeAsFailures, StatusInRangeFailureClass};

let trace_layer = TraceLayer::new(SharedClassifier::new(StatusInRangeAsFailures::new(400..=599)))
    .on_failure(|failure, _: Duration, _: &tracing::Span| {
        match failure {
            StatusInRangeFailureClass::StatusCode(status) => {
                warn!("ControlPlane.http_request_trace warning: {status}");
            }
            StatusInRangeFailureClass::Error(_err) => {
                // something else
            }
        }
    })
    .on_response(|response: &Response<_>, _: Duration, _: &tracing::Span| { ... })
    .make_span_with(|request: &Request<_>| { ... });

Leave a Comment