- Published on
Log Spring WebClient Request and Response
- Authors
- Name
In this blog, We will look into logging Spring WebClient Request and Response calls including body.
If you never used Spring WebClient before, here is my story on Spring WebClient To Send Synchronous Http Requests.
The default HttpClient used by WebClient is Netty. There is no straight way to log request and response including body in a specific format using Netty. So we are gonna change WebClient default http client to jetty.
Jetty uses listeners to provide hooks for all possible request and response events. We will use these hooks to log request and response content.
Below is how we do it:
Include below dependency to pom.xml
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-reactive-httpclient</artifactId>
<version>4.0.2</version>
</dependency>
LoggerService
We will create a LoggerService with only one method which takes Jetty request object. Inside the method we will add request and response hooks to log events.
//Added inline comments for detailed explanation.
package com.commons.rest.client.service;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.http.HttpField;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
@Slf4j
@Service
public class LoggerService {
/**
*
* @param inboundRequest of type jetty client Request Object
* @return return same request again after preparing logging
*/
public Request logRequestResponse(Request inboundRequest) {
// Created a string builder object to append logs belongs to specific request
StringBuilder logBuilder = new StringBuilder();
// Request Logging
/*
This listener will be invoked when the request is being processed in order to be sent.
When this is invoked we are appending uri and method to string builder with line break.
*/
inboundRequest.onRequestBegin(request -> logBuilder.append("Request: \n")
.append("URI: ")
.append(request.getURI())
.append("\n")
.append("Method: ")
.append(request.getMethod()));
/*
This listener will be invoked when the request headers are ready to be sent.
When this is invoked we are appending Headers to string builder with proper format.
make sure debugging is enabled if not remove log.isDebugEnabled condition.
*/
inboundRequest.onRequestHeaders(request -> {
if(log.isDebugEnabled()) {
logBuilder.append("\nHeaders:\n");
for (HttpField header : request.getHeaders()) {
logBuilder.append("\t\t").append(header.getName()).append(" : ").append(header.getValue()).append("\n");
}
}
});
/*
This listener will be invoked when request content has been sent successfully.
When this is invoked we are converting bytes to String and appending to StringBuilder .
make sure debugging is enabled if not remove log.isDebugEnabled condition.
*/
inboundRequest.onRequestContent((request, content) -> {
if(log.isDebugEnabled()) {
var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
logBuilder.append("Body: \n\t")
.append(bufferAsString);
}
});
// Response Logging
/*
This listener will be invoked when the response containing HTTP version, HTTP status code and reason has been received and parsed.
When this is invoked we are appending response status to string builder with line break for formatting.
*/
inboundRequest.onResponseBegin(response -> logBuilder.append("\nResponse:\n")
.append("Status: ")
.append(response.getStatus())
.append("\n"));
/*
This listener will be invoked when the response headers are received and parsed.
When this is invoked we are appending Headers to string builder with proper format.
make sure debugging is enabled if not remove log.isDebugEnabled condition.
*/
inboundRequest.onResponseHeaders(response -> {
logBuilder.append("Headers:\n");
for (HttpField header : response.getHeaders()) {
logBuilder.append("\t\t").append(header.getName()).append(" : ").append(header.getValue()).append("\n");
}
});
/*
This listener will be invoked when response content has been received and parsed.
When this is invoked we are converting bytes to String and appending to StringBuilder .
make sure debugging is enabled if not remove log.isDebugEnabled condition.
*/
inboundRequest.onResponseContent(((response, content) -> {
if(log.isDebugEnabled()) {
var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
logBuilder.append("Response Body:\n").append(bufferAsString);
}
}));
inboundRequest.onResponseSuccess(response -> log.info(logBuilder.toString()));
inboundRequest.onRequestFailure((request, throwable) -> log.error(logBuilder.toString(), throwable));
inboundRequest.onResponseFailure((response, throwable) -> log.error(logBuilder.toString(), throwable));
return inboundRequest;
}
}
WebClient Instance
We need to create a new Jetty Http Client and we should override new request method. We need to pass request to loggerservice for logging.
Then we need to give the created jetty http client to Spring WebClient, so that WebClient will use jetty as http client for http requests. We need to autowire WebClient in the service where we would like to use it.
package com.commons.config;
import com.samaritan.commons.rest.client.service.LoggerService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import java.net.URI;
@Slf4j
@Configuration
public class CommonsConfig {
@Bean
protected WebClient.Builder webClientBuilder(LoggerService loggerService) {
HttpClient httpClient = new HttpClient() {
@Override
public Request newRequest(URI uri) {
Request request = super.newRequest(uri);
return loggerService.logRequestResponse(request);
}
};
return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient));
}
}
Here is the Request interface which has more hooks if you like to configure.
I hope this helps you to log request and response content using Spring WebClient.