How to log spring-webflux WebClient request + response details (bodies, headers, elasped_time)?
Asked Answered
I

2

9

Basically, I want to log a request/response informations in one log containing bodies/headers with a Spring WebClient.

With Spring RestTemplate we can do it with a ClientHttpRequestInterceptor. I find about ExchangeFilterFunction for Spring WebClient but haven't managed to do something similar in a clean way. We can use this filter and log the request AND THEN the response but I need both on the same log trace.

Moreover, I haven't managed to get the response body with ExchangeFilterFunction.ofResponseProcessor method.

I expect a log like this (current implementation working with a ClientHttpRequestInterceptor) with all the informations I need:

{
    "@timestamp": "2019-05-14T07:11:29.089+00:00",
    "@version": "1",
    "message": "GET https://awebservice.com/api",
    "logger_name": "com.sample.config.resttemplate.LoggingRequestInterceptor",
    "thread_name": "http-nio-8080-exec-5",
    "level": "TRACE",
    "level_value": 5000,
    "traceId": "e65634ee6a7c92a7",
    "spanId": "7a4d2282dbaf7cd5",
    "spanExportable": "false",
    "X-Span-Export": "false",
    "X-B3-SpanId": "7a4d2282dbaf7cd5",
    "X-B3-ParentSpanId": "e65634ee6a7c92a7",
    "X-B3-TraceId": "e65634ee6a7c92a7",
    "parentId": "e65634ee6a7c92a7",
    "method": "GET",
    "uri": "https://awebservice.com/api",
    "body": "[Empty]",
    "elapsed_time": 959,
    "status_code": 200,
    "status_text": "OK",
    "content_type": "text/html",
    "response_body": "{"message": "Hello World!"}"
}

Does anyone manage to do something like this with Spring WebClient ? Or how would one proceed to track request/reponses issue with a Spring WebClient ?

Ignoble answered 15/5, 2019 at 8:35 Comment(0)
I
5

You can use filter(), something like this:

this.webClient = WebClient.builder().baseUrl("your_url")
            .filter(logRequest())
            .filter(logResponse())
            .build();

private ExchangeFilterFunction logRequest() {
    return (clientRequest, next) -> {
        log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
        return next.exchange(clientRequest);
    };
}

private ExchangeFilterFunction logResponse() {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        log.info("Response: {}", clientResponse.headers().asHttpHeaders().get("property-header"));
        return Mono.just(clientResponse);
    });
}
Iggy answered 21/5, 2019 at 22:7 Comment(6)
I know about this but this does not generate a log that contains request information AND response information in the same log. I'm looking a way to log both at the same time. This answer generates one log for request and one log for response.Ignoble
You cannot log a request body with an Exchange FilterScreeching
@StefanHaberl yes you can, using the .ofRequestProcessor() in function ExchangeFilterFunctionVesuvianite
@Vesuvianite may you share code on how one may log request bodies using ExchangeFilterFunction.ofRequestProcessor()?Retentivity
@Retentivity public static ExchangeFilterFunction logRequest(final StringBuilder logTrail) { return ExchangeFilterFunction.ofRequestProcessor(request -> { //request.body() return Mono.just(request); }); }Vesuvianite
And how it's possible to log the body the request ? In the above example only headers are loggedMarylouisemaryly
T
0

I don't think you can call .bodyToMono twice (once in the filter and then again where you use the client), so you might not be able to log that in the filter. As for the other details...

The WebClient config:

@Configuration
class MyClientConfig {

    @Bean
    fun myWebClient(): WebClient {
        return WebClient
            .builder()
            .baseUrl(myUrl)
            .filter(MyFilter())
            .build()
    }
}

The filter:

class MyFilter : ExchangeFilterFunction {

    override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
        return next.exchange(request).flatMap { response ->

            // log whenever you want here...
            println("request: ${request.url()}, response: ${response.statusCode()}")

            Mono.just(response)
        }
    }
}
Tew answered 27/4, 2021 at 6:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.