之前利用HttpServletRequest.getInputStream()和RequestWrapper实现了请求的requestBody获取,现在提出将一个请求的RequestBody和ResponseBody都提出来并打印日志&落入数据库,以便统计和查找问题。
查找资料后确定两种技术方案
1. 使用AOP对所有Controller的方法进行环绕通知处理;
2. 使用Filter拦截所有的Request和Response,并获取body。
最后选择了第二种方式,具体实现记录如下。
具体实现
日志记录过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
public class RequestFilter implements Filter{ private static final String LOG_FORMATTER_IN = "请求路径:{%s},请求方法:{%s},参数:{%s},来源IP:{%s},请求开始时间{%s},返回:{%s},请求结束时间{%s},用时:{%s}ms,操作类型:{%s},操作人:{%s}" ; public static final String USER_TOKEN_REDIS_PREFIX = "token_prefix" ; private static final Logger log = LoggerFactory.getLogger(RequestFilter. class ); //request拦截的conten-type列表 private List<String> contentTypes; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; //请求路径 String path = httpServletRequest.getRequestURI(); String method = httpServletRequest.getMethod(); //所有请求参数的Map Map<String,String> paramMap = new HashMap<>(); //请求的真实IP String requestedIP = RequestUtils.getRealIP(httpServletRequest); //是否拦截并包装请求,如果需要拦截则会获取RequestBody,一般为application/json才拦截 boolean filterRequestFlag = checkFilter(request.getContentType()); if (filterRequestFlag) { httpServletRequest = new MyRequestBodyReaderWrapper(httpServletRequest); } //获取所有queryString和requestBody Map<String, String> requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest); if (requestParamMap != null && !requestParamMap.isEmpty()){ paramMap.putAll(requestParamMap); } //获取header参数 Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest); if (headerMap != null && !headerMap.isEmpty()){ paramMap.putAll(headerMap); } //获取路径参数 Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest); if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){ paramMap.putAll(uriTemplateMap); } //包装Response,重写getOutputStream()和getWriter()方法,并用自定义的OutputStream和Writer来拦截和保存ResponseBody MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse); //请求开始时间 Long dateStart = System.currentTimeMillis(); //Spring通过DispatchServlet处理请求 chain.doFilter(httpServletRequest, responseWrapper); //请求结束时间 Long dateEnd = System.currentTimeMillis(); String responseBody; if (responseWrapper.getMyOutputStream() == null ){ if (responseWrapper.getMyWriter() != null ){ responseBody = responseWrapper.getMyWriter().getContent(); //一定要flush,responseBody会被复用 responseWrapper.getMyWriter().myFlush(); } } else { responseBody = responseWrapper.getMyOutputStream().getBuffer(); //一定要flush,responseBody会被复用 responseWrapper.getMyOutputStream().myFlush(); } String params = JSONObject.toJSONString(paramMap); log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart)); } /** * 判断请求/返回是否为application/json * 是则进行拦截, * 否则退出 * @param contentType 请求/响应类型 */ private boolean checkFilter(String contentType) { boolean filterFlag = false ; //是否继续拦截 for (String p : getContentTypes()) { if (StringUtils.contains(contentType, p)){ filterFlag = true ; } } if (StringUtils.isEmpty(contentType)){ filterFlag = true ; } return filterFlag; } } |
Request包装器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
/** * HttpServletRequest的包装器,为了在拦截器阶段获取requestBody且不妨碍SpringMVC再次获取requestBody */ @Slf4j public class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper { //存放JSON数据主体 private final byte [] body; public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException { super (request); body = getBody(request).getBytes(Charset.forName( "UTF-8" )); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader( new InputStreamReader( this .getInputStream())); } /** * 获取请求Body */ public static String getBody(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null ; BufferedReader reader = null ; try { inputStream = request.getInputStream(); reader = new BufferedReader( new InputStreamReader(inputStream, Charset.forName( "UTF-8" ))); String line; while ((line = reader.readLine()) != null ) { sb.append(line); } } catch (IOException e) { log.error( "MyRequestBodyReaderWrapper.getBody()异常-->" ,e); } finally { if (inputStream != null ) { try { inputStream.close(); } catch (IOException e) { log.error( "MyRequestBodyReaderWrapper.getBody()异常-->" ,e); } } if (reader != null ) { try { reader.close(); } catch (IOException e) { log.error( "MyRequestBodyReaderWrapper.getBody()异常-->" ,e); } } } return sb.toString(); } } |
RequestUtils
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
/** * 请求工具类 */ public class RequestUtils { private static final Logger logger = LoggerFactory.getLogger(RequestUtils. class ); /** * 获取所有的请求头 * @param request * @return */ public static Map<String,String> getHeaders(HttpServletRequest request){ Map<String,String> headerMap = new HashMap<>(); List<String> headers = getCommonHeaders(); headers.add( "Postman-Token" ); headers.add( "Proxy-Connection" ); headers.add( "X-Lantern-Version" ); headers.add( "Cookie" ); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); if (headers.contains(headerName)){ continue ; } headerMap.put(headerName,request.getHeader(headerName)); } return headerMap; } /** * 获取请求的路径参数 * @param request * @return */ public static Map<String, String> getUriTemplateVar(HttpServletRequest request) { NativeWebRequest webRequest = new ServletWebRequest(request); Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return uriTemplateVars; } /** * 获取请求的真实IP * @param request * @return */ public static String getRealIP(HttpServletRequest request) { String ip = request.getHeader( "X-Forwarded-For" ); if (StringUtils.isNotEmpty(ip) && ! "unKnown" .equalsIgnoreCase(ip)) { //多次反向代理后会有多个ip值,第一个ip才是真实ip int index = ip.indexOf( "," ); if (index != - 1 ) { return ip.substring( 0 , index); } else { return ip; } } ip = request.getHeader( "X-Real-IP" ); if (StringUtils.isNotEmpty(ip) && ! "unKnown" .equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); } /** * 从Request中获取所有的请求参数,包括GET/POST/PATCH等请求,不包括路径参数 * @param request * @return */ public static Map<String,String> getRequestParamMap(HttpServletRequest request) { Map<String,String> paramMap = new HashMap<>(); //获取QueryString中的参数,GET方式 或application/x-www-form-urlencoded Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request); if (queryParamMap != null ){ paramMap.putAll(queryParamMap); } //获取Body中的参数,POST/PATCH等方式,application/json Map<String,String> bodyParamMap = null ; try { //当为POST请求且 application/json时,request被RequestFilter处理为wrapper类 if (!(request instanceof MyRequestBodyReaderWrapper)){ return paramMap; } MyRequestBodyReaderWrapper readerWrapper = (MyRequestBodyReaderWrapper) request; String requestBody = new String(readerWrapper.getBody(), "UTF-8" ); if (com.zhongan.health.common.utils.StringUtils.isNotBlank(requestBody)){ /** * 该方法为了避免 fastJson在 反序列化多层json时,改变对象顺序 */ bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField); } } catch (Exception e) { logger.error( "获取请求Body异常-->" ,e); } if (bodyParamMap != null ){ paramMap.putAll(bodyParamMap); } return paramMap; } private static List<String> getCommonHeaders(){ List<String> headers = new ArrayList<>(); Class<HttpHeaders> clazz = HttpHeaders. class ; Field[] fields = clazz.getFields(); for (Field field : fields) { field.setAccessible( true ); if (field.getType().toString().endsWith( "java.lang.String" ) && Modifier.isStatic(field.getModifiers())){ try { headers.add((String) field.get(HttpHeaders. class )); } catch (IllegalAccessException e) { logger.error( "反射获取属性值异常-->" ,e); } } } return headers; } } |
Response包装器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** *该包装器主要是重写getOutputStream()和getWriter()方法,给调用者返回自定义的OutputStream和Writer,以便参与输出的过程并记录保存responseBody。 */ public class MyResponseWrapper extends HttpServletResponseWrapper { private ResponsePrintWriter writer; private MyServletOutputStream out; public MyResponseWrapper(HttpServletResponse response) { super (response); } @Override public ServletOutputStream getOutputStream() throws IOException { //一定要先判断当前out为空才能去新建out对象,否则一次请求会出现多个out对象 if (out == null ){ out = new MyServletOutputStream( super .getOutputStream()); } return out; } @Override public PrintWriter getWriter() throws IOException { //一定要先判断当前writer为空才能去新建writer对象,否则一次请求会出现多个writer对象 if (writer == null ){ writer = new ResponsePrintWriter( super .getWriter()); } return writer; } public ResponsePrintWriter getMyWriter() { return writer; } public MyServletOutputStream getMyOutputStream(){ return out; } } |
自定义Writer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/** *自定义Writer,重写write方法,并记录保存ResponseBody */ public class ResponsePrintWriter extends PrintWriter{ private StringBuffer buffer; public ResponsePrintWriter(PrintWriter out) { super (out); buffer = new StringBuffer(); } public String getContent(){ return buffer == null ? null : buffer.toString(); } @Override public void flush() { super .flush(); } //清空buffer,以便下一次重新使用 public void myFlush(){ buffer = null ; } @Override public void write( char [] buf, int off, int len) { super .write(buf, off, len); char [] destination = new char [len]; System.arraycopy(buf,off,destination, 0 ,len); buffer.append(destination); } @Override public void write(String s) { super .write(s); buffer.append(s); } } |
自定义OutputStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/** * 自定义输出流包装器,重写write方法,并记录保存ResponseBody */ public class MyServletOutputStream extends ServletOutputStream { private ServletOutputStream outputStream; private StringBuffer buffer; public MyServletOutputStream(ServletOutputStream outputStream) { this .outputStream = outputStream; buffer = new StringBuffer(); } @Override public void write( int b) throws IOException { outputStream.write(b); } @Override public void write( byte [] b, int off, int len) throws IOException { outputStream.write(b, off, len); byte [] bytes = new byte [len]; System.arraycopy(b, off, bytes, 0 , len); buffer.append( new String(bytes, "UTF-8" )); } @Override public void write( byte [] b) throws IOException { outputStream.write(b); } @Override public void flush() throws IOException { super .flush(); } //清空buffer,以便下一次重新使用 public void myFlush(){ outputStream = null ; buffer = null ; } public String getBuffer() { if (buffer != null ){ return buffer.toString(); } return null ; } } |
总结一下
-
Request.getInputStream
一次请求中只能被调用一次; -
Response.getOutputStream()
无法获取ResponseBody; -
Response
的输出有两种方式,都需要考虑到并重写
getOutputStream().write()
getWrite().write()
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/panyongcsd/article/details/80990397