|
@@ -0,0 +1,139 @@
|
|
|
|
|
+package net.yyc.common.log;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.util.CharsetUtil;
|
|
|
|
|
+import net.yyc.common.log.config.LogConfig;
|
|
|
|
|
+import net.yyc.common.log.vo.LogMessage;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.lang.NonNull;
|
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
+import org.springframework.web.filter.OncePerRequestFilter;
|
|
|
|
|
+import org.springframework.web.util.ContentCachingRequestWrapper;
|
|
|
|
|
+import org.springframework.web.util.ContentCachingResponseWrapper;
|
|
|
|
|
+
|
|
|
|
|
+import javax.servlet.FilterChain;
|
|
|
|
|
+import javax.servlet.ServletException;
|
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.util.Enumeration;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * WEB请求日志数据拼装
|
|
|
|
|
+ *
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+public class RequestLoggingFilter extends OncePerRequestFilter {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 请求EVENT
|
|
|
|
|
+ */
|
|
|
|
|
+ private static final String LOG_EVENT = "REQ";
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 打印日志开关
|
|
|
|
|
+ */
|
|
|
|
|
+ private final boolean enable;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * body最大长度
|
|
|
|
|
+ */
|
|
|
|
|
+ private final int maxLength;
|
|
|
|
|
+
|
|
|
|
|
+ private static final int LOGGING_BODY_MAX_LENGTH = 20 * 1024;
|
|
|
|
|
+
|
|
|
|
|
+ private static final String FILTER_ACTUATOR = "/actuator/";
|
|
|
|
|
+ private static final String FILTER_FAVICON = "/favicon.ico";
|
|
|
|
|
+
|
|
|
|
|
+ RequestLoggingFilter(LogConfig logConfig) {
|
|
|
|
|
+ this.enable = logConfig.isRequestEnable();
|
|
|
|
|
+ this.maxLength = logConfig.getRequestMaxLength();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 是否处理异步请求,false为处理
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * The default return value is "true", which means the filter will not be
|
|
|
|
|
+ * invoked during subsequent async dispatches. If "false", the filter will
|
|
|
|
|
+ * be invoked during async dispatches with the same guarantees of being
|
|
|
|
|
+ * invoked only once during a input within a single thread.
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected boolean shouldNotFilterAsyncDispatch() {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 核心方法,记录日志
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response,
|
|
|
|
|
+ @NonNull FilterChain filterChain)
|
|
|
|
|
+ throws ServletException, IOException {
|
|
|
|
|
+ if (!enable || request.getRequestURI().startsWith(FILTER_ACTUATOR) || Objects.equals(request.getRequestURI(), FILTER_FAVICON)) {
|
|
|
|
|
+ filterChain.doFilter(request, response);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
|
|
+ ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request, maxLength);
|
|
|
|
|
+ ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ filterChain.doFilter(requestWrapper, responseWrapper);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("日志记录异常", e);
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ log.info(createMessage(requestWrapper, responseWrapper, startTime));
|
|
|
|
|
+ responseWrapper.copyBodyToResponse();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 拼接日志
|
|
|
|
|
+ */
|
|
|
|
|
+ private String createMessage(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, long startTime) {
|
|
|
|
|
+ LogMessage logMsg = new LogMessage();
|
|
|
|
|
+ logMsg.setEvent(LOG_EVENT + (isAsyncDispatch(request) ? "_ASYNC" : ""));
|
|
|
|
|
+ logMsg.setMethod(request.getMethod());
|
|
|
|
|
+ logMsg.setRequestUri(StringUtils.isEmpty(request.getQueryString()) ?
|
|
|
|
|
+ request.getRequestURI() : request.getRequestURI() + "?" + request.getQueryString());
|
|
|
|
|
+ logMsg.setIp(request.getRemoteAddr());
|
|
|
|
|
+ logMsg.setReqHeaders(assembleReqHeaders(request));
|
|
|
|
|
+ if (request.getContentLength() <= LOGGING_BODY_MAX_LENGTH) {
|
|
|
|
|
+ logMsg.setReqBody(new String(request.getContentAsByteArray(), CharsetUtil.CHARSET_UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+ logMsg.setHttpStatus(response.getStatus());
|
|
|
|
|
+ logMsg.setRespHeaders(assembleRespHeaders(response));
|
|
|
|
|
+ if (response.getContentSize() <= LOGGING_BODY_MAX_LENGTH) {
|
|
|
|
|
+ logMsg.setRespBody(new String(response.getContentAsByteArray(), CharsetUtil.CHARSET_UTF_8));
|
|
|
|
|
+ }
|
|
|
|
|
+ logMsg.setDuration(System.currentTimeMillis() - startTime);
|
|
|
|
|
+ return logMsg.buildMessage();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String assembleReqHeaders(HttpServletRequest request) {
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ Enumeration<String> enumeration = request.getHeaderNames();
|
|
|
|
|
+ while (enumeration.hasMoreElements()) {
|
|
|
|
|
+ String key = enumeration.nextElement();
|
|
|
|
|
+ sb.append(key).append("=").append(request.getHeader(key))
|
|
|
|
|
+ .append(LogMessage.DEFAULT_LOG_FIELD_SEPARATOR_2);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (sb.length() == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return sb.substring(0, sb.length() - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String assembleRespHeaders(HttpServletResponse response) {
|
|
|
|
|
+ return response.getHeaderNames().stream()
|
|
|
|
|
+ .map(
|
|
|
|
|
+ key -> key + "=" + response.getHeader(key)
|
|
|
|
|
+ ).collect(
|
|
|
|
|
+ Collectors.joining(LogMessage.DEFAULT_LOG_FIELD_SEPARATOR_2)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|