好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringMVC框架中使用Filter实现请求日志打印方式

之前利用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测试数据mon.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

查看更多关于SpringMVC框架中使用Filter实现请求日志打印方式的详细内容...

  阅读:19次