Skip to content

Instantly share code, notes, and snippets.

@elit69
Forked from int128/RequestAndResponseLoggingFilter.java
Last active October 5, 2019 05:00
Show Gist options
  • Save elit69/a9e037fd70c02b384fbdf5974c67bb17 to your computer and use it in GitHub Desktop.
Save elit69/a9e037fd70c02b384fbdf5974c67bb17 to your computer and use it in GitHub Desktop.
Spring Web filter for logging request and response
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