Skip to content

Instantly share code, notes, and snippets.

@ozinal
Created May 30, 2020 21:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ozinal/b2bb68326794b26a808f391e860b05cc to your computer and use it in GitHub Desktop.
Save ozinal/b2bb68326794b26a808f391e860b05cc to your computer and use it in GitHub Desktop.
Spring Boot Request and Response Logging Interceptor
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
/**
* Spring Web filter for logging request and response.
*
* @author Hidetake Iwata
* @see org.springframework.web.filter.AbstractRequestLoggingFilter
* @see ContentCachingRequestWrapper
* @see ContentCachingResponseWrapper
*/
@Slf4j
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
MediaType.valueOf("text/*"),
MediaType.APPLICATION_FORM_URLENCODED,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML,
MediaType.valueOf("application/*+json"),
MediaType.valueOf("application/*+xml"),
MediaType.MULTIPART_FORM_DATA
);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
try {
beforeRequest(request, response);
filterChain.doFilter(request, response);
}
finally {
afterRequest(request, response);
response.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
logRequestHeader(request, request.getRemoteAddr() + "|>");
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
logRequestBody(request, request.getRemoteAddr() + "|>");
logResponse(response, request.getRemoteAddr() + "|<");
}
}
private static void logRequestHeader(ContentCachingRequestWrapper request, String prefix) {
val queryString = request.getQueryString();
if (queryString == null) {
log.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
} else {
log.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{} {}: {}", prefix, headerName, headerValue)));
log.info("{}", prefix);
}
private static void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
val content = request.getContentAsByteArray();
if (content.length > 0) {
logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
}
}
private static void logResponse(ContentCachingResponseWrapper response, String prefix) {
val status = response.getStatus();
log.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{} {}: {}", prefix, headerName, headerValue)));
log.info("{}", prefix);
val content = response.getContentAsByteArray();
if (content.length > 0) {
logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
}
}
private static void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
val mediaType = MediaType.valueOf(contentType);
val visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
if (visible) {
try {
val contentString = new String(content, contentEncoding);
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{} {}", prefix, line));
} catch (UnsupportedEncodingException e) {
log.info("{} [{} bytes content]", prefix, content.length);
}
} else {
log.info("{} [{} bytes content]", prefix, content.length);
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment