1 簡介 在Spring MVC中,我們有時需要記錄一下請求和返回的內容,方便出現問題時排查。比較Header、Request Body等。這些在Controller也可以記錄,但在Filter中會更方便。而我們使用的是OncePerRequestFilter。 2 記錄請求 2.1 流重覆讀的問題 ...
1 簡介
在Spring MVC
中,我們有時需要記錄一下請求和返回的內容,方便出現問題時排查。比較Header、Request Body等。這些在Controller
也可以記錄,但在Filter
中會更方便。而我們使用的是OncePerRequestFilter
。
2 記錄請求
2.1 流重覆讀的問題
可以通過下麵的代碼來讀取請求Body:
byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream());
log.info("request body = {}", new String(requestBody, StandardCharsets.UTF_8));
但是這裡從流讀取了一次內容後,後續不可再讀了。這就造成了真正處理請求的時候,報錯失敗,我們需要把Request對象改造成可重覆讀的類。
2.2 通過Wrapper解決流重覆讀的問題
為了可以讓流重覆讀,加了以下Wrapper:
public class PkslowRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public PkslowRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
這裡主要在構造時讀了流,然後存在變數body
里,每次返迴流的時候從body
構造回去即可。
在Filter中使用這個Wrapper如下:
PkslowRequestWrapper request = new PkslowRequestWrapper(req);
ServletInputStream servletInputStream = request.getInputStream();
String body = StreamUtils.copyToString(servletInputStream, Charset.defaultCharset());
log.info("Request Body(PkslowRequestWrapper): {}", body);
2.3 內置Filter
其實,針對Request,Spring Boot提供了內置的Filter可以直接記錄請求,使用如下:
package com.pkslow.springboot.common.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
@Configuration
public class PkslowConfig {
@Bean
public CommonsRequestLoggingFilter loggingFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeHeaders(true);
filter.setIncludeClientInfo(true);
filter.setIncludePayload(true);
filter.setIncludeQueryString(true);
filter.setAfterMessagePrefix("CommonsRequestLoggingFilter Request: ");
return filter;
}
}
但要開debug
級別的日誌才會打出來。
logging:
level:
root: debug
日誌如下:
DEBUG 20356 --- [nio-8080-exec-1] o.s.w.f.CommonsRequestLoggingFilter : Before request [POST /hello/pkslow, client=127.0.0.1, headers=[authorization:"Basic xxxxxx", content-length:"37", host:"localhost:8080", connection:"Keep-Alive", user-agent:"Apache-HttpClient/4.5.13 (Java/17.0.5)", accept-encoding:"gzip,deflate", Content-Type:"application/json;charset=UTF-8"]]
3 記錄返回
返回也是一樣,有流不可重覆讀的問題,使用Spring
自帶的ContentCachingResponseWrapper
即可。
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(res);
log.info("Response Code: {}", response.getStatus());
String responseBody = new String(response.getContentAsByteArray(), response.getCharacterEncoding());
log.info("Response Body: {}", responseBody);
response.copyBodyToResponse();
特別註意一定要調用copyBodyToResponse()
這個方法,不然無法返回body給請求端了。
4 記錄時間
記錄整個請求的處理時間請參考: Java如何測量方法執行時間
5 測試
測試一下:
POST http://localhost:8080/hello/pkslow
Content-Type: application/json
Authorization: Basic xxxxxx
{
"id": 999,
"value": "content"
}
執行日誌結果如下:
6 總結
也可使用ContentCachingRequestWrapper
來解決請求流不可重覆讀的問題,但這個Wrapper是有限制的,具體可以看它源碼。也有人提了Issue。
代碼請看GitHub: https://github.com/LarryDpk/pkslow-samples