统一日志记录
项目中经常会遇到日志打印,通常的做法是使用Filter统一拦截处理。但如果想打印body里的数据,会出现不能再次读取的问题,servlet的requestbody以及response的body只能被读取一次,一旦流被读取了,就无法再次读取了。
推荐使用Spring本身提供的Wrapper类来解决此问题,当然也可以自己封装个Wrapper类,继承HttpServletRequestWrapper即可。本文介绍Spring提供的Wrapper的使用。
caching wrapper
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
spring提供了ContentCachingRequestWrapper以及ContentCachingResponseWrapper两个类,来解决这类问题。 读取完body之后再设置回去。
wrappedResponse.copyBodyToResponse();
Filter
完整的filter如下,可参考修改使用:
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
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.net.URLDecoder;
import java.util.Enumeration;
import java.util.Objects;
import java.util.UUID;
@Component
public class LogFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(getClass());
public static final String TRACE_ID = "traceId";
public static final int SLOW_TIME_MILLIS = 5000;
public static final int LOG_MAX_LENGTH = 1024;
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String url = null;
String headers = null;
String traceId = "";
StopWatch watch = null;
try {
url = getCompleteUrl(request);
// 暂时不打印headers内容
// headers = getHeaders(request).toString();
// 设置traceId
traceId = request.getHeader(TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = UUID.randomUUID().toString();
}
MDC.put(TRACE_ID, traceId);
request = new ContentCachingRequestWrapper(request);
response = new ContentCachingResponseWrapper(response);
watch = new StopWatch();
watch.start();
logger.info("start request, ip:{} host:{} traceId:{} url:{} header:{} body:{}", getRemortIP(request),
request.getRemoteHost(), traceId, url, headers, getRequestBody(request));
filterChain.doFilter(request, response);
} finally {
if (watch != null) {
watch.stop();
if (watch.getTotalTimeMillis() > SLOW_TIME_MILLIS && !url.endsWith(".js")) {
logger.info("end slow request, ip:{} host:{} traceId:{} url:{} header:{} cost:{} ms resp:{}", getRemortIP(request),
request.getRemoteHost(), traceId, url, headers, watch.getTotalTimeMillis(), getResponseBody(response));
} else {
logger.info("end request, ip:{} host:{} traceId:{} url:{} header:{} cost:{} ms resp:{}", getRemortIP(request),
request.getRemoteHost(), traceId, url, headers, watch.getTotalTimeMillis(), getResponseBody(response));
}
}
MDC.clear();
updateResponse(response);
}
}
@Override
public void destroy() {
}
private String getRequestBody(HttpServletRequest request) {
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper == null) {
return "";
}
return getBody(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
}
private String getBody(byte[] contentAsByteArray, String characterEncoding) {
String body = "";
try {
body = IOUtils.toString(contentAsByteArray, characterEncoding);
if (body == null) {
return body;
}
// 最多打印1024字符
///body = body.replaceAll("
", "");
if (body.length() > LOG_MAX_LENGTH) {
body = body.substring(0, LOG_MAX_LENGTH) + "...";
}
} catch (Exception e) {
// NOOP
}
return body;
}
/**
* 获取 response body
*
* @param response
* @return 最长返回1024字符,避免打印过长
*/
private String getResponseBody(HttpServletResponse response) {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (responseWrapper == null) {
return "";
}
return getBody(responseWrapper.getContentAsByteArray(), "UTF-8");
}
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
private String getCompleteUrl(HttpServletRequest request) {
String url = request.getRequestURL().toString();
String queryString = "";
if (StringUtils.isNotEmpty(request.getQueryString())) {
try {
queryString = URLDecoder.decode(request.getQueryString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("URLDecoder.decode error:" + e.getMessage(), e);
}
url = url + "?" + queryString;
}
return url;
}
/**
* 获取header信息
*
* @param request
* @return headersMap
*/
private JSONObject getHeaders(HttpServletRequest request) {
JSONObject map = new JSONObject();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
private String getRemortIP(HttpServletRequest request) {
if (request.getHeader("x-forwarded-for") == null) {
return request.getRemoteAddr();
}
return request.getHeader("x-forwarded-for");
}
}