轉(zhuǎn)載請注明來源 賴賴的博客
導(dǎo)語
總要造一下輪子才知道別人的輪子有多厲害
最近筆者遇到一個問題:在SpringMVC框架下蜒秤,沒有直接的方式可以打印完整的AccessLog,因為request body 和response body是通過inputstream和outputstream來封裝的菩佑,在使用過很多人提供的方式后瘾婿,發(fā)現(xiàn)效果總是不盡人意徙菠,所以自己摸索了一下消返,完成了這個功能
請慎重在生產(chǎn)環(huán)境使用這個功能,因為打印完整的AccessLog是一個很大的消耗
項目工程目錄結(jié)構(gòu)和代碼獲取地址
https://github.com/laiyijie/spring-access-log-filter
詳解核心類AccessLogFilter
完整代碼如下:
public class AccessLogFilter extends OncePerRequestFilter {
private static final Logger logger = LogManager.getLogger(AccessLogFilter.class);
private String usernameKey = "username";
private Integer payloadMaxLength = 1024;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Long startTime = System.currentTimeMillis();
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
request.getCharacterEncoding());
String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
response.getCharacterEncoding());
responseWrapper.copyBodyToResponse();
BLog.accessJsonLogBuilder()
.addRequestPayLoad(requestPayload)
.addResponsePayLoad(responsePayload)
.put(request, usernameKey)
.put(response)
.put("_COST_", System.currentTimeMillis() - startTime)
.log();
}
private String getPayLoad(byte[] buf, String characterEncoding) {
String payload = "";
if (buf == null) return payload;
if (buf.length > 0) {
int length = Math.min(buf.length, getPayloadMaxLength());
try {
payload = new String(buf, 0, length, characterEncoding);
} catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
}
return payload;
}
public String getUsernameKey() {
return usernameKey;
}
public void setUsernameKey(String usernameKey) {
this.usernameKey = usernameKey;
}
public Integer getPayloadMaxLength() {
return payloadMaxLength;
}
public void setPayloadMaxLength(Integer payloadMaxLength) {
this.payloadMaxLength = payloadMaxLength;
}
}
其核心思想是實現(xiàn)了OncePerRequestFilter這個虛基類劣领,而這個虛基類是實現(xiàn)了Filter接口姐军,并且要基于SpringMVC框架的,因此這個方法只能適用于SpringMVC工程
核心方法是doFilterInternal
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Long startTime = System.currentTimeMillis();
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
request.getCharacterEncoding());
String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
response.getCharacterEncoding());
responseWrapper.copyBodyToResponse();
BLog.accessJsonLogBuilder()
.addRequestPayLoad(requestPayload)
.addResponsePayLoad(responsePayload)
.put(request, usernameKey)
.put(response)
.put("_COST_", System.currentTimeMillis() - startTime)
.log();
}
首先通過
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
這兩個方式對請求和返回進(jìn)行包裝(讓body可以被緩存),這樣來解決inputstream不能多次讀取的問題
調(diào)用
filterChain.doFilter(requestWrapper, responseWrapper);
來執(zhí)行其他的filter之后
可以通過
String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
request.getCharacterEncoding());
String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
response.getCharacterEncoding());
這種方式取出request和response的payload奕锌,不要忘記
responseWrapper.copyBodyToResponse();
重新寫入response
最后打印出整個log