I've started using WebClient and I'm adding logging of request/response and I'm using filter method when constructing WebClient:
WebClient.builder()
.baseUrl(properties.getEndpoint())
.filter((request, next) -> {
// logging
request.body()
})
.build();
I'm able to access url, http method, headers but I've a problem with getting raw request body as body()
method of request returns BodyInserter
(BodyInserter<?, ? super ClientHttpRequest> body()
.
How to convert BodyInserter
to String
representation of request body? Alternatively, how to properly log whole request/response while also being able to hash potential credentials in it?
WebClient is a reactive and non-blocking interface for HTTP requests, based on Spring WebFlux. It has a functional, fluent API with reactive types for declarative composition. Behind the scenes, WebClient calls an HTTP client. Reactor Netty is the default and reactive HttpClient of Jetty is also supported.
WebClient – retrieve() vs exchange()exchange method provides more control and details like status, headers and response body, etc. retrieve() method provides automatic error signal (e.g. 4xx and 5xx). No automatic error signal is available for exchange() method and we need to check status code and handle it.
You can create your own wrapper/proxy class around the JSON encoder and intercept the serialized body before it is sent into the intertubes.
This blog post shows how to log the JSON payloads of WebClient requests and responses
Specifically, you would extend the encodeValue
method (or encodeValues
in case of streaming data) of Jackson2JsonEncoder
. Then you can do with that data what you wish, such as logging etc. And you could even do this conditionally based on environment/profile
This custom logging-encoder can be specified when creating the WebClient
, by codecs:
CustomBodyLoggingEncoder bodyLoggingEncoder = new CustomBodyLoggingEncoder();
WebClient.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyLoggingEncoder);
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
})
...
Update 2020/7/3:
Here is a rushed example applying the same principle but for a decoder:
public class LoggingJsonDecoder extends Jackson2JsonDecoder {
private final Consumer<byte[]> payloadConsumer;
public LoggingJsonEncoder(final Consumer<byte[]> payloadConsumer) {
this.payloadConsumer = payloadConsumer;
}
@Override
public Mono<Object> decodeToMono(final Publisher<DataBuffer> input, final ResolvableType elementType, final MimeType mimeType, final Map<String, Object> hints) {
// Buffer for bytes from each published DataBuffer
final ByteArrayOutputStream payload = new ByteArrayOutputStream();
// Augment the Flux, and intercept each group of bytes buffered
final Flux<DataBuffer> interceptor = Flux.from(input)
.doOnNext(buffer -> bufferBytes(payload, buffer))
.doOnComplete(() -> payloadConsumer.accept(payload.toByteArray()));
// Return the original method, giving our augmented Publisher
return super.decodeToMono(interceptor, elementType, mimeType, hints);
}
private void bufferBytes(final ByteArrayOutputStream bao, final DataBuffer buffer) {
try {
bao.write(ByteUtils.extractBytesAndReset(buffer));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
You would configure that along with the encoder using the codecs
builder method on WebClient
.
Of course this above only works assuming your data is being deserialized to a Mono. But override other methods if you need it. Also I'm just stdout'ing the resulting JSON there, but you could pass in a Consumer<String>
or something for the decoder to send the string to for example, or just log from there ; up to you.
A word of warning that in it's current form this is going to be doubling your memory usage, as it's buffering the entire response. If you can send that byte data off to another process/thread to write to log file or some output stream (or Flux even) immediately, you could avoid buffering the whole payload in memory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With