系统需要异步记录每一个HTTP请求的信息,包括请求路径、请求参数、响应状态、返回参数、请求耗时等信息。

    1. import com.alibaba.fastjson.JSON;
    2. import com.lenton.inner.service.AsyncHttpTraceLogService;
    3. import lombok.Data;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.apache.commons.io.Charsets;
    6. import org.apache.commons.io.IOUtils;
    7. import org.jetbrains.annotations.NotNull;
    8. import org.springframework.core.Ordered;
    9. import org.springframework.http.HttpStatus;
    10. import org.springframework.web.filter.OncePerRequestFilter;
    11. import org.springframework.web.util.ContentCachingRequestWrapper;
    12. import org.springframework.web.util.ContentCachingResponseWrapper;
    13. import org.springframework.web.util.WebUtils;
    14. import javax.servlet.FilterChain;
    15. import javax.servlet.FilterConfig;
    16. import javax.servlet.ServletException;
    17. import javax.servlet.http.HttpServletRequest;
    18. import javax.servlet.http.HttpServletResponse;
    19. import java.io.IOException;
    20. import java.net.URI;
    21. import java.net.URISyntaxException;
    22. import java.time.LocalDateTime;
    23. import java.util.Objects;
    24. /**
    25. * @author mori
    26. * @date 2021-08-16 10:26:00
    27. */
    28. @Slf4j
    29. public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered {
    30. private static final String IGNORE_CONTENT_TYPE = "multipart/form-data";
    31. private String[] excludedUris;
    32. private final AsyncHttpTraceLogService asyncHttpTraceLogService;
    33. public HttpTraceLogFilter(AsyncHttpTraceLogService asyncHttpTraceLogService) {
    34. this.asyncHttpTraceLogService = asyncHttpTraceLogService;
    35. }
    36. @Override
    37. protected void initFilterBean() throws ServletException {
    38. FilterConfig config = getFilterConfig();
    39. if (config != null) {
    40. excludedUris = config.getInitParameter("excludedUris").split(",");
    41. }
    42. }
    43. @Override
    44. public int getOrder() {
    45. return Ordered.LOWEST_PRECEDENCE - 10;
    46. }
    47. @Override
    48. protected void doFilterInternal(@NotNull HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    49. throws ServletException, IOException {
    50. if (isExcludedUri(request.getServletPath())) {
    51. filterChain.doFilter(request, response);
    52. return;
    53. }
    54. if (!(request instanceof ContentCachingRequestWrapper)) {
    55. request = new ContentCachingRequestWrapper(request);
    56. }
    57. if (!(response instanceof ContentCachingResponseWrapper)) {
    58. response = new ContentCachingResponseWrapper(response);
    59. }
    60. int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
    61. long startTime = System.currentTimeMillis();
    62. try {
    63. filterChain.doFilter(request, response);
    64. status = response.getStatus();
    65. } finally {
    66. String path = request.getRequestURI();
    67. if (!Objects.equals(IGNORE_CONTENT_TYPE, request.getContentType())) {
    68. String requestBody = IOUtils.toString(request.getInputStream(), Charsets.UTF_8);
    69. log.info(requestBody);
    70. // 记录日志
    71. HttpTraceLog traceLog = new HttpTraceLog();
    72. traceLog.setPath(path);
    73. traceLog.setMethod(request.getMethod());
    74. long latency = System.currentTimeMillis() - startTime;
    75. traceLog.setResponseTime(latency);
    76. traceLog.setTime(LocalDateTime.now().toString());
    77. traceLog.setParameterMap(JSON.toJSONString(request.getParameterMap()));
    78. traceLog.setStatus(status);
    79. traceLog.setRequestBody(getRequestBody(request));
    80. traceLog.setResponseBody(getResponseBody(response));
    81. asyncHttpTraceLogService.writeLog(JSON.toJSONString(traceLog));
    82. // log.info("Http trace log: {}", JSON.toJSONString(traceLog));
    83. }
    84. updateResponse(response);
    85. }
    86. }
    87. private String getRequestBody(HttpServletRequest request) {
    88. String requestBody = "";
    89. ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
    90. if (wrapper != null) {
    91. try {
    92. requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), Charsets.UTF_8.toString());
    93. } catch (IOException e) {
    94. // NOOP
    95. }
    96. }
    97. return requestBody;
    98. }
    99. private String getResponseBody(HttpServletResponse response) {
    100. String responseBody = "";
    101. ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
    102. if (wrapper != null) {
    103. try {
    104. responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), Charsets.UTF_8.toString());
    105. } catch (IOException e) {
    106. // NOOP
    107. }
    108. }
    109. return responseBody;
    110. }
    111. private void updateResponse(HttpServletResponse response) throws IOException {
    112. ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
    113. Objects.requireNonNull(responseWrapper).copyBodyToResponse();
    114. }
    115. @Data
    116. private static class HttpTraceLog {
    117. private String path;
    118. private String method;
    119. private String parameterMap;
    120. private String requestBody;
    121. private String responseBody;
    122. private Long responseTime;
    123. private String time;
    124. private Integer status;
    125. }
    126. private boolean isExcludedUri(String uri) {
    127. if (excludedUris == null || excludedUris.length <= 0) {
    128. return false;
    129. }
    130. for (String ex : excludedUris) {
    131. uri = uri.trim();
    132. ex = ex.trim();
    133. if (uri.toLowerCase().matches(ex.toLowerCase().replace("*", ".*"))) {
    134. return true;
    135. }
    136. }
    137. return false;
    138. }
    139. }

    将过滤器注册到mvc中

    1. import org.springframework.boot.web.servlet.FilterRegistrationBean;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    6. import javax.servlet.DispatcherType;
    7. /**
    8. * mvc配置
    9. * @author mori
    10. * @date 2021-07-27 14:51:00
    11. */
    12. @EnableWebMvc
    13. @Configuration
    14. public class WebMvcConfig implements WebMvcConfigurer {
    15. @Bean
    16. public FilterRegistrationBean<HttpTraceLogFilter> httpTraceLogFilter(AsyncHttpTraceLogService asyncHttpTraceLogService) {
    17. FilterRegistrationBean<HttpTraceLogFilter> registration = new FilterRegistrationBean<>();
    18. registration.setDispatcherTypes(DispatcherType.REQUEST);
    19. registration.setFilter(new HttpTraceLogFilter(asyncHttpTraceLogService));
    20. registration.setName("httpTraceLogFilter");
    21. registration.addUrlPatterns("/*");
    22. registration.addInitParameter("excludedUris",
    23. "/,/swagger*,/webjars/*,/v2/*,/doc.html,/csrf,/favicon.ico");
    24. return registration;
    25. }
    26. }

    配置异步线程池

    1. import org.springframework.context.annotation.Bean;
    2. import org.springframework.context.annotation.Configuration;
    3. import org.springframework.scheduling.annotation.EnableAsync;
    4. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    5. import java.util.concurrent.Executor;
    6. import java.util.concurrent.ThreadPoolExecutor;
    7. /**
    8. * @author mori
    9. * @date 2021-08-16 11:40:00
    10. */
    11. @Configuration
    12. @EnableAsync
    13. public class ExecutorConfig {
    14. @Bean
    15. public Executor asyncLogServiceExecutor() {
    16. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    17. // 配置核心线程数
    18. executor.setCorePoolSize(5);
    19. // 配置最大线程数
    20. executor.setMaxPoolSize(20);
    21. // 配置队列大小
    22. executor.setQueueCapacity(400);
    23. // 配置线程池中的线程的名称前缀
    24. executor.setThreadNamePrefix("http-trace-log-thread-");
    25. // rejection-policy:当pool已经达到max size的时候,如何处理新任务
    26. // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
    27. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    28. // 执行初始化
    29. executor.initialize();
    30. return executor;
    31. }
    32. }

    异步写入Service

    1. public interface AsyncHttpTraceLogService {
    2. /**
    3. * 写日志
    4. *
    5. * @param info 信息
    6. */
    7. void writeLog(String info);
    8. }
    1. import com.lenton.inner.service.AsyncHttpTraceLogService;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.scheduling.annotation.Async;
    4. import org.springframework.stereotype.Service;
    5. /**
    6. * @author mori
    7. * @date 2021-08-16 11:52:00
    8. */
    9. @Slf4j
    10. @Service
    11. public class AsyncHttpTraceLogServiceImpl implements AsyncHttpTraceLogService {
    12. @Async("asyncLogServiceExecutor")
    13. @Override
    14. public void writeLog(String info) {
    15. log.info("async service: " + info);
    16. }
    17. }