-
-
Save jkuipers/24ffbf8a5ba26c0177629e9aba492bfa to your computer and use it in GitHub Desktop.
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.http.HttpHeaders; | |
import org.springframework.http.HttpRequest; | |
import org.springframework.http.MediaType; | |
import org.springframework.http.client.ClientHttpRequestExecution; | |
import org.springframework.http.client.ClientHttpRequestInterceptor; | |
import org.springframework.http.client.ClientHttpResponse; | |
import org.springframework.util.StreamUtils; | |
import java.io.IOException; | |
import java.nio.charset.Charset; | |
import java.nio.charset.StandardCharsets; | |
import java.nio.charset.UnsupportedCharsetException; | |
/** | |
* Allows logging outgoing requests and the corresponding responses. | |
* Requires the use of a {@link org.springframework.http.client.BufferingClientHttpRequestFactory} to log | |
* the body of received responses. | |
*/ | |
public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { | |
protected Logger requestLogger; | |
protected Logger responseLogger; | |
/** | |
* Creates an interceptor with request logger {@code spring.web.client.MessageTracing.sent} | |
* and response logger {@code spring.web.client.MessageTracing.received}, loosely following | |
* Spring-WS logger naming conventions. | |
*/ | |
public LoggingClientHttpRequestInterceptor() { | |
this(LoggerFactory.getLogger("spring.web.client.MessageTracing.sent"), | |
LoggerFactory.getLogger("spring.web.client.MessageTracing.received")); | |
} | |
/** | |
* @param requestLogger the logger used to log sent requests | |
* @param responseLogger the logger used to log received responses | |
*/ | |
public LoggingClientHttpRequestInterceptor(Logger requestLogger, Logger responseLogger) { | |
this.requestLogger = requestLogger; | |
this.responseLogger = responseLogger; | |
} | |
@Override | |
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { | |
logRequest(request, body); | |
ClientHttpResponse response = execution.execute(request, body); | |
logResponse(request, response); | |
return response; | |
} | |
protected void logRequest(HttpRequest request, byte[] body) { | |
if (requestLogger.isDebugEnabled()) { | |
StringBuilder builder = new StringBuilder("Sending ").append(request.getMethod()).append(" request to ").append(request.getURI()); | |
if (body.length > 0 && hasTextBody(request.getHeaders())) { | |
String bodyText = new String(body, determineCharset(request.getHeaders())); | |
builder.append(": [").append(bodyText).append("]"); | |
} | |
requestLogger.debug(builder.toString()); | |
} | |
} | |
protected void logResponse(HttpRequest request, ClientHttpResponse response) { | |
if (responseLogger.isDebugEnabled()) { | |
try { | |
StringBuilder builder = new StringBuilder("Received \"") | |
.append(response.getRawStatusCode()).append(" ").append(response.getStatusText()).append("\" response for ") | |
.append(request.getMethod()).append(" request to ").append(request.getURI()); | |
HttpHeaders responseHeaders = response.getHeaders(); | |
long contentLength = responseHeaders.getContentLength(); | |
if (contentLength != 0) { | |
if (hasTextBody(responseHeaders) && !isMockedResponse(response)) { | |
String bodyText = StreamUtils.copyToString(response.getBody(), determineCharset(responseHeaders)); | |
builder.append(": [").append(bodyText).append("]"); | |
} else { | |
if (contentLength == -1) { | |
builder.append(" with content of unknown length"); | |
} else { | |
builder.append(" with content of length ").append(contentLength); | |
} | |
MediaType contentType = responseHeaders.getContentType(); | |
if (contentType != null) { | |
builder.append(" and content type ").append(contentType); | |
} else { | |
builder.append(" and unknown content type"); | |
} | |
} | |
} | |
responseLogger.debug(builder.toString()); | |
} catch (IOException e) { | |
responseLogger.warn("Failed to log response for {} request to {}", request.getMethod(), request.getURI(), e); | |
} | |
} | |
} | |
protected boolean hasTextBody(HttpHeaders headers) { | |
MediaType contentType = headers.getContentType(); | |
if (contentType != null) { | |
if ("text".equals(contentType.getType())) { | |
return true; | |
} | |
String subtype = contentType.getSubtype(); | |
if (subtype != null) { | |
return "xml".equals(subtype) || "json".equals(subtype) || | |
subtype.endsWith("+xml") || subtype.endsWith("+json"); | |
} | |
} | |
return false; | |
} | |
protected Charset determineCharset(HttpHeaders headers) { | |
MediaType contentType = headers.getContentType(); | |
if (contentType != null) { | |
try { | |
Charset charSet = contentType.getCharset(); | |
if (charSet != null) { | |
return charSet; | |
} | |
} catch (UnsupportedCharsetException e) { | |
// ignore | |
} | |
} | |
return StandardCharsets.UTF_8; | |
} | |
private boolean isMockedResponse(ClientHttpResponse response) { | |
return "MockClientHttpResponse".equals(response.getClass().getSimpleName()); | |
} | |
} |
Thx 👍
Thanks, this helped me a lot :-)
Thanks, this is so helpful!
Hi, I have used a version of this code doing something like
log.info("Response body: {}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
this exhausts the body and makes it so there is no body when the actual resources want to use it.
Is there a better way to do it ?
for example this spock test fails.
def 'fetch access token integration test'() {
setup:
RestTemplate rt = new RestTemplate()
RequestResponseLoggingInterceptor ri = new RequestResponseLoggingInterceptor()
rt.setInterceptors([ri])
when:
ResponseEntity<String> responseEntity= rt.exchange("http://google.com", HttpMethod.GET, null, String.class)
then:
responseEntity.hasBody()
}
@rmargam register a BufferingClientHttpRequestFactory
with your RestTemplate
as stated in the type-level JavaDoc to avoid that issue. It wraps the response in a class that buffers the response body internally, so that it can be consumed multiple times.
One way to automatically do that for all your RestTemplate
beans is shown here, assuming you're using Spring Boot's RestTemplateBuilder
: https://gist.github.com/jkuipers/cd462d163c2c1c81f34092de12f7bab2
Thank you for this, so helpful. Finally a solution that works with the BufferingClientHttpRequestFactory that do not close the stream when reading.
hi, how can i use this interceptor in my project? I am trying to store response and request into mongodb, but i am confused how i can use this class. Can you help me please?
thank you @jkuipers .very helpful
Thank you, it helped me a lot!