Forked from int128/RequestAndResponseLoggingFilter.java
Last active
October 5, 2019 05:00
-
-
Save elit69/a9e037fd70c02b384fbdf5974c67bb17 to your computer and use it in GitHub Desktop.
Spring Web filter for logging request and response
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.IOException; | |
import java.io.UnsupportedEncodingException; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.Enumeration; | |
import java.util.List; | |
import javax.annotation.PostConstruct; | |
import javax.servlet.FilterChain; | |
import javax.servlet.ServletContext; | |
import javax.servlet.ServletException; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.MediaType; | |
import org.springframework.stereotype.Component; | |
import org.springframework.web.filter.OncePerRequestFilter; | |
import org.springframework.web.util.ContentCachingRequestWrapper; | |
import org.springframework.web.util.ContentCachingResponseWrapper; | |
@Component | |
public class LoggingHttpFilter extends OncePerRequestFilter { | |
@Autowired | |
private ServletContext servletContext; | |
@Value("${custom.logging.uri.prefix:}") | |
private String prefix; | |
@Value("#{'${custom.logging.uri.ignore:/favicon.ico,/health,/swagger-ui.html,/configuration/ui,/swagger-resources,/v2/api-docs,/configuration/security}'.split(',')}") | |
private List<String> ignoreURIs; | |
@Value("#{'${custom.logging.prefix.ignore:/webjars}'.split(',')}") | |
private List<String> ignorePrefixs; | |
@Value("#{'${custom.logging.request.header.ignore:accept-charset}'.split(',')}") | |
private List<String> ignoreHeaders; | |
@Value("${custom.logging.max.content.length:4000}") | |
private Integer maxContentLength; | |
@Value("${custom.logging.is.log.request.header:false}") | |
private Boolean isLogRequestHeader; | |
@Value("${custom.logging.is.log.response.header:false}") | |
private Boolean isLogResponseHeader; | |
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingHttpFilter.class); | |
@PostConstruct | |
private void addContextPath() { | |
String contextPath = servletContext.getContextPath(); | |
//create new list ignoreURIs | |
//addon context path | |
List<String> newIgnoreURIs = new ArrayList<String>(); | |
for (String uri : ignoreURIs) { | |
if(!uri.contains(contextPath)) { | |
newIgnoreURIs.add(contextPath + uri); | |
} | |
} | |
if(!newIgnoreURIs.isEmpty()) { | |
ignoreURIs = newIgnoreURIs; | |
} | |
LOGGER.info("ignoreURIs {}", ignoreURIs); | |
//create new list ignorePrefixs | |
//addon context path | |
List<String> newIgnorePrefixs = new ArrayList<String>(); | |
for (String uri : ignorePrefixs) { | |
if(!uri.contains(contextPath)) { | |
newIgnorePrefixs.add(contextPath + uri); | |
} | |
} | |
if(!newIgnorePrefixs.isEmpty()) { | |
ignorePrefixs = newIgnorePrefixs; | |
} | |
LOGGER.info("ignorePrefixs {}", ignorePrefixs); | |
//set prefix = contextPath + prefix | |
if(!prefix.contains(contextPath)) { | |
prefix = contextPath + prefix; | |
} | |
LOGGER.info("prefix {}", prefix); | |
} | |
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 { | |
filterChain.doFilter(request, response); | |
} finally { | |
afterRequest(request, response); | |
response.copyBodyToResponse(); | |
} | |
} | |
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) { | |
String requestUri = request.getRequestURI(); | |
Boolean notInIgnorePrefixList = !ignorePrefixs.stream().anyMatch( | |
(eachPrefix) -> requestUri.startsWith(eachPrefix)); | |
if (!ignoreURIs.contains(requestUri) //if not ignore uri | |
&& notInIgnorePrefixList //not in ignore prefixs list | |
&& requestUri.startsWith(prefix)) { //and is start with prefix Ex: /api) | |
logAccess(request, "http_access"); | |
logRequestHeader(request, "http_request_header"); | |
logRequestBody(request, "http_request_body"); | |
logResponseHeader(response, "http_response_header"); | |
logResponseBody(response, "http_response_body"); | |
} | |
} | |
private void logAccess(ContentCachingRequestWrapper request, String prefix) { | |
if (LOGGER.isInfoEnabled()) { | |
String queryString = request.getQueryString(); | |
StringBuilder url = new StringBuilder(request.getMethod()) | |
.append(" ") | |
.append(request.getRequestURI()); | |
if (queryString == null) { | |
url.append(" by ").append(request.getRemoteAddr()); | |
} else { | |
url.append("?").append(queryString).append(" by ").append(request.getRemoteAddr()); | |
} | |
LOGGER.info("{} {}", prefix, url); | |
} | |
} | |
private void logRequestHeader(ContentCachingRequestWrapper request, String prefix) { | |
if (LOGGER.isInfoEnabled() && isLogRequestHeader) { | |
StringBuilder builder = new StringBuilder(); | |
Enumeration<String> headerNames = request.getHeaderNames(); | |
while (headerNames.hasMoreElements()) { | |
String name = headerNames.nextElement(); | |
if(!ignoreHeaders.contains(name)) { | |
builder.append(name).append("=["); | |
Enumeration<String> values = request.getHeaders(name); | |
while (values.hasMoreElements()) { | |
String value = values.nextElement(); | |
builder.append(value).append(", "); | |
} | |
builder.setLength(builder.length() - 2); | |
builder.append("], "); | |
} | |
} | |
if (builder.length() > 2) { | |
builder.setLength(builder.length() - 2); | |
} | |
LOGGER.info("{} {}", prefix, builder); | |
} | |
} | |
private void logRequestBody(ContentCachingRequestWrapper request, String prefix) { | |
if (LOGGER.isInfoEnabled()) { | |
byte[] content = request.getContentAsByteArray(); | |
if (content.length > 0) { | |
logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix, ""); | |
} else { | |
LOGGER.info("{} {}", prefix, ""); | |
} | |
} | |
} | |
private void logResponseHeader(ContentCachingResponseWrapper response, String prefix) { | |
if(LOGGER.isInfoEnabled() && isLogResponseHeader) { | |
StringBuilder builder = new StringBuilder(); | |
Collection<String> headerNames = response.getHeaderNames(); | |
for (String headerName : headerNames) { | |
Collection<String> headers = response.getHeaders(headerName); | |
for (String header : headers) { | |
builder.append(headerName).append(": ").append(header).append(", "); | |
} | |
} | |
LOGGER.info("{} {}", prefix, builder); | |
} | |
} | |
private void logResponseBody(ContentCachingResponseWrapper response, String prefix) { | |
if (LOGGER.isInfoEnabled()) { | |
StringBuilder status = new StringBuilder("status ") | |
.append(response.getStatus()) | |
.append(" ") | |
.append(HttpStatus.valueOf(response.getStatus()).getReasonPhrase()); | |
byte[] content = response.getContentAsByteArray(); | |
if (content.length > 0) { | |
logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix, status.toString()); | |
} else { | |
LOGGER.info("{} {}", prefix, status); | |
} | |
} | |
} | |
private void logContent(byte[] content, String contentType, String contentEncoding, String prefix, String suffix) { | |
try { | |
MediaType mediaType = MediaType.valueOf(contentType); | |
Boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType)); | |
if (visible) { | |
String contentString = new String(content, contentEncoding); | |
Integer length = Math.min(maxContentLength, contentString.length()); | |
contentString = contentString.substring(0, length); | |
LOGGER.info("{} {} {}", prefix, contentString, suffix); | |
} else { | |
LOGGER.info("{} [{} bytes content] {}", prefix, content.length, suffix); | |
} | |
} catch (UnsupportedEncodingException | IllegalArgumentException e) { | |
LOGGER.error("{} {}", e.getClass(), e.getMessage()); | |
LOGGER.info("{} [{} bytes content] {}", prefix, content.length, suffix); | |
} | |
} | |
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