系统需要异步记录每一个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
*/
@Slf4j
public 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;
}
@Override
protected void initFilterBean() throws ServletException {
FilterConfig config = getFilterConfig();
if (config != null) {
excludedUris = config.getInitParameter("excludedUris").split(",");
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
protected 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();
}
@Data
private 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
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public 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
@EnableAsync
public class ExecutorConfig {
@Bean
public 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
@Service
public class AsyncHttpTraceLogServiceImpl implements AsyncHttpTraceLogService {
@Async("asyncLogServiceExecutor")
@Override
public void writeLog(String info) {
log.info("async service: " + info);
}
}