系统需要异步记录每一个HTTP请求的信息,包括请求路径、请求参数、响应状态、返回参数、请求耗时等信息。
import com.alibaba.fastjson.JSON;import com.lenton.inner.service.AsyncHttpTraceLogService;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.apache.commons.io.Charsets;import org.apache.commons.io.IOUtils;import org.jetbrains.annotations.NotNull;import org.springframework.core.Ordered;import org.springframework.http.HttpStatus;import org.springframework.web.filter.OncePerRequestFilter;import org.springframework.web.util.ContentCachingRequestWrapper;import org.springframework.web.util.ContentCachingResponseWrapper;import org.springframework.web.util.WebUtils;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.net.URI;import java.net.URISyntaxException;import java.time.LocalDateTime;import java.util.Objects;/*** @author mori* @date 2021-08-16 10:26:00*/@Slf4jpublic class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered {private static final String IGNORE_CONTENT_TYPE = "multipart/form-data";private String[] excludedUris;private final AsyncHttpTraceLogService asyncHttpTraceLogService;public HttpTraceLogFilter(AsyncHttpTraceLogService asyncHttpTraceLogService) {this.asyncHttpTraceLogService = asyncHttpTraceLogService;}@Overrideprotected void initFilterBean() throws ServletException {FilterConfig config = getFilterConfig();if (config != null) {excludedUris = config.getInitParameter("excludedUris").split(",");}}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE - 10;}@Overrideprotected void doFilterInternal(@NotNull HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {if (isExcludedUri(request.getServletPath())) {filterChain.doFilter(request, response);return;}if (!(request instanceof ContentCachingRequestWrapper)) {request = new ContentCachingRequestWrapper(request);}if (!(response instanceof ContentCachingResponseWrapper)) {response = new ContentCachingResponseWrapper(response);}int status = HttpStatus.INTERNAL_SERVER_ERROR.value();long startTime = System.currentTimeMillis();try {filterChain.doFilter(request, response);status = response.getStatus();} finally {String path = request.getRequestURI();if (!Objects.equals(IGNORE_CONTENT_TYPE, request.getContentType())) {String requestBody = IOUtils.toString(request.getInputStream(), Charsets.UTF_8);log.info(requestBody);// 记录日志HttpTraceLog traceLog = new HttpTraceLog();traceLog.setPath(path);traceLog.setMethod(request.getMethod());long latency = System.currentTimeMillis() - startTime;traceLog.setResponseTime(latency);traceLog.setTime(LocalDateTime.now().toString());traceLog.setParameterMap(JSON.toJSONString(request.getParameterMap()));traceLog.setStatus(status);traceLog.setRequestBody(getRequestBody(request));traceLog.setResponseBody(getResponseBody(response));asyncHttpTraceLogService.writeLog(JSON.toJSONString(traceLog));// log.info("Http trace log: {}", JSON.toJSONString(traceLog));}updateResponse(response);}}private String getRequestBody(HttpServletRequest request) {String requestBody = "";ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);if (wrapper != null) {try {requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), Charsets.UTF_8.toString());} catch (IOException e) {// NOOP}}return requestBody;}private String getResponseBody(HttpServletResponse response) {String responseBody = "";ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);if (wrapper != null) {try {responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), Charsets.UTF_8.toString());} catch (IOException e) {// NOOP}}return responseBody;}private void updateResponse(HttpServletResponse response) throws IOException {ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);Objects.requireNonNull(responseWrapper).copyBodyToResponse();}@Dataprivate static class HttpTraceLog {private String path;private String method;private String parameterMap;private String requestBody;private String responseBody;private Long responseTime;private String time;private Integer status;}private boolean isExcludedUri(String uri) {if (excludedUris == null || excludedUris.length <= 0) {return false;}for (String ex : excludedUris) {uri = uri.trim();ex = ex.trim();if (uri.toLowerCase().matches(ex.toLowerCase().replace("*", ".*"))) {return true;}}return false;}}
将过滤器注册到mvc中
import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.servlet.DispatcherType;/*** mvc配置* @author mori* @date 2021-07-27 14:51:00*/@EnableWebMvc@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {@Beanpublic FilterRegistrationBean<HttpTraceLogFilter> httpTraceLogFilter(AsyncHttpTraceLogService asyncHttpTraceLogService) {FilterRegistrationBean<HttpTraceLogFilter> registration = new FilterRegistrationBean<>();registration.setDispatcherTypes(DispatcherType.REQUEST);registration.setFilter(new HttpTraceLogFilter(asyncHttpTraceLogService));registration.setName("httpTraceLogFilter");registration.addUrlPatterns("/*");registration.addInitParameter("excludedUris","/,/swagger*,/webjars/*,/v2/*,/doc.html,/csrf,/favicon.ico");return registration;}}
配置异步线程池
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;/*** @author mori* @date 2021-08-16 11:40:00*/@Configuration@EnableAsyncpublic class ExecutorConfig {@Beanpublic Executor asyncLogServiceExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 配置核心线程数executor.setCorePoolSize(5);// 配置最大线程数executor.setMaxPoolSize(20);// 配置队列大小executor.setQueueCapacity(400);// 配置线程池中的线程的名称前缀executor.setThreadNamePrefix("http-trace-log-thread-");// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 执行初始化executor.initialize();return executor;}}
异步写入Service
public interface AsyncHttpTraceLogService {/*** 写日志** @param info 信息*/void writeLog(String info);}
import com.lenton.inner.service.AsyncHttpTraceLogService;import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;/*** @author mori* @date 2021-08-16 11:52:00*/@Slf4j@Servicepublic class AsyncHttpTraceLogServiceImpl implements AsyncHttpTraceLogService {@Async("asyncLogServiceExecutor")@Overridepublic void writeLog(String info) {log.info("async service: " + info);}}
