New LoggableDispatcherServlet class:

package com.hsh.common.dispatch;


import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.*;

@Order(Ordered.HIGHEST_PRECEDENCE)//最高优先级 方便拦截404什么的
public class LoggableDispatcherServlet extends DispatcherServlet {

    private static final Logger logger = LoggerFactory.getLogger("HttpLogger");

    private static final ObjectMapper mapper = new ObjectMapper();

    private static final long serialVersionUID = -2151909516770706554L;

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//        if ("GET".equals(request.getMethod())) {
//            super.doDispatch(request, response);
//            return;
//        }
        List<String> curlItemList = new ArrayList<>();
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
        setThrowExceptionIfNoHandlerFound(true);
        ObjectNode rootNode = mapper.createObjectNode();
        ObjectNode reqNode = mapper.createObjectNode();
        ObjectNode resNode = mapper.createObjectNode();
        String method = request.getMethod();
        curlItemList.add(MessageFormat.format("-X ''{0}''", method));
        rootNode.put("method", method);
        String requestUrl = request.getRequestURL().toString();
        rootNode.put("url", requestUrl);
        rootNode.put("remoteAddr", request.getRemoteAddr());
        rootNode.put("x-forwarded-for", request.getHeader("x-forwarded-for"));
        rootNode.set("request", reqNode);
        rootNode.set("response", resNode);
        reqNode.set("headers", mapper.valueToTree(getRequestHeaders(request)));

        try {
            reqNode.set("query", mapper.valueToTree(request.getParameterMap()));
            if ("GET".equals(method)) {
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append("'").append(requestUrl);
                Enumeration<String> parameterNames = request.getParameterNames();
                if (parameterNames.hasMoreElements()) {
                    stringBuilder.append("?");
                }
                while (parameterNames.hasMoreElements()) {
                    String name = parameterNames.nextElement();
                    String[] values = request.getParameterValues(name);
                    for (String value : values) {
                        stringBuilder.append(name).append("=").append(value).append("&");
                    }
                }
                stringBuilder.append("'");
                curlItemList.add(stringBuilder.toString());
                super.doDispatch(request, responseWrapper);
            } else {
                curlItemList.add("'" + requestUrl + "'");
                if (isFormPost(request)) {
                    ContentCachingRequestWrapper bufferedServletRequestWrapper = new ContentCachingRequestWrapper(request);
                    reqNode.set("body", mapper.valueToTree(request.getParameterMap()));
                    reqNode.put("bodyIsJson", false);
                    StringBuilder stringBuilder = new StringBuilder();
                    Enumeration<String> parameterNames = request.getParameterNames();
                    while (parameterNames.hasMoreElements()) {
                        String name = parameterNames.nextElement();
                        String[] values = request.getParameterValues(name);
                        for (String value : values) {
                            stringBuilder.append(name).append("=").append(value).append("&");
                        }
                    }
                    curlItemList.add(MessageFormat.format("--data-raw ''{0}''", stringBuilder.toString()));
                    super.doDispatch(bufferedServletRequestWrapper, responseWrapper);
                } else if (isJsonPost(request)) {
                    BufferedServletRequestWrapper bufferedServletRequestWrapper = new BufferedServletRequestWrapper(request);
                    ServletInputStream inputStream = bufferedServletRequestWrapper.getInputStream();
                    byte[] contentAsByteArray = IOUtils.toByteArray(inputStream);
                    reqNode.set("body", mapper.readTree(contentAsByteArray));
                    reqNode.put("bodyIsJson", true);
                    curlItemList.add(MessageFormat.format("--data-binary ''{0}''", mapper.readTree(contentAsByteArray)));
                    super.doDispatch(bufferedServletRequestWrapper, responseWrapper);
                } else if (isTextPost(request) || isXmlPost(request)) {
                    BufferedServletRequestWrapper bufferedServletRequestWrapper = new BufferedServletRequestWrapper(request);
                    ServletInputStream inputStream = bufferedServletRequestWrapper.getInputStream();
                    byte[] contentAsByteArray = IOUtils.toByteArray(inputStream);
                    reqNode.put("body", new String(contentAsByteArray));
                    reqNode.put("bodyIsJson", false);
                    curlItemList.add(MessageFormat.format("--data-binary ''{0}''", new String(contentAsByteArray)));
                    super.doDispatch(bufferedServletRequestWrapper, responseWrapper);
                } else if (isMediaPost(request)) {
                    reqNode.put("body", "Media Request Body ContentLength = " + request.getContentLengthLong());
                    reqNode.put("bodyIsJson", false);
                    super.doDispatch(request, responseWrapper);
                } else {
                    reqNode.put("body", "Unknown Request Body ContentLength = " + request.getContentLengthLong());
                    reqNode.put("bodyIsJson", false);
                    super.doDispatch(request, responseWrapper);
                }
            }
            HandlerExecutionChain handlerExecutionChain = getHandler(request);
            if (handlerExecutionChain == null) {
                //手动判断是不是404 不走系统流程 直接处理 因为会重定向/error
                resNode.put("status", HttpStatus.NOT_FOUND.value());
                logger.info(rootNode.toString());
                response.setStatus(HttpStatus.NOT_FOUND.value());
                PrintWriter writer = response.getWriter();
                writer.write("Request path not found");
                writer.flush();
                writer.close();
                return;
            }
            System.out.println(handlerExecutionChain);
        } finally {
            byte[] responseWrapperContentAsByteArray = responseWrapper.getContentAsByteArray();
            responseWrapper.copyBodyToResponse();//这里有顺序 必须先读body 然后再调用这个方法 才能继续读
            resNode.put("status", response.getStatus());
            Map<String, Object> responseHeaders = getResponseHeaders(response);

            //这里判断错误拦截是不是吧url改成error了 如果是就做一下替换 替换的值是错误拦截器写到header里面的
            String url = rootNode.get("url").asText();
            if (url.endsWith("/error")) {
                String path = (String) responseHeaders.get("x-error-path");
                if (!ObjectUtils.isEmpty(path)) {
                    rootNode.put("url", url.replace("/error", path));
                }
            }
            resNode.set("headers", mapper.valueToTree(responseHeaders));
            if (isProtoBufPost(responseWrapper) || "GET".equals(request.getMethod())) {
            } else {
                try {
                    resNode.set("body", mapper.readTree(responseWrapperContentAsByteArray));
                    resNode.put("bodyIsJson", true);
                } catch (Exception e) {
                    resNode.put("body", new String(responseWrapperContentAsByteArray));
                    resNode.put("bodyIsJson", false);
                }
            }
            logger.info(rootNode.toPrettyString());
            getCurlRequestHeaders(request, curlItemList);
            logger.info("curl " + CollectionUtil.join(curlItemList, "\n   "));
        }
    }

    private Map<String, Object> getRequestHeaders(HttpServletRequest request) {
        Map<String, Object> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;

    }

    private void getCurlRequestHeaders(HttpServletRequest request, List<String> list) {
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            if (!headerValue.contains("multipart/form-data")) {
                list.add(MessageFormat.format("-H ''{0}: {1}''", headerName, headerValue));
            }
        }
    }

    private Map<String, Object> getResponseHeaders(HttpServletResponse response) {
        Map<String, Object> headers = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String headerName : headerNames) {
            headers.put(headerName, response.getHeader(headerName));
        }
        return headers;
    }

    private boolean isFormPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        return (contentType != null && contentType.contains("x-www-form"));
    }

    private boolean isMediaPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("stream") || contentType.contains("image") || contentType.contains("video") || contentType.contains("audio");
        }
        return false;
    }

    private boolean isTextPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("text/plain") || contentType.contains("text/xml") || contentType.contains("text/html");
        }
        return false;
    }

    private boolean isJsonPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("application/json");
        }
        return false;
    }

    private boolean isXmlPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("application/xml");
        }
        return false;
    }

    private boolean isProtoBufPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("application") && contentType.contains("protobuf");
        }
        return false;
    }

    private boolean isProtoBufPost(HttpServletResponse response) {
        String contentType = response.getContentType();
        if (contentType != null) {
            return contentType.contains("application") && contentType.contains("protobuf");
        }
        return false;
    }

    private boolean isMultipartFormDataPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return contentType.contains("multipart/form-data");
        }
        return false;
    }

    class BufferedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream inputStream;
        private ServletInputStream is;

        public BufferedServletInputStream(byte[] buffer, ServletInputStream is) {
            this.is = is;
            this.inputStream = new ByteArrayInputStream(buffer);
        }

        @Override
        public int available() {
            return inputStream.available();
        }

        @Override
        public int read() {
            return inputStream.read();
        }

        @Override
        public int read(byte[] b, int off, int len) {
            return inputStream.read(b, off, len);
        }

        @Override
        public boolean isFinished() {
            return is.isFinished();
        }

        @Override
        public boolean isReady() {
            return is.isReady();
        }

        @Override
        public void setReadListener(ReadListener listener) {
            is.setReadListener(listener);
        }
    }

    class BufferedServletRequestWrapper extends HttpServletRequestWrapper {
        private byte[] buffer;
        private ServletInputStream is;

        public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            this.is = request.getInputStream();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byteArrayOutputStream.write(IOUtils.toByteArray(is));
            this.buffer = byteArrayOutputStream.toByteArray();
        }

        @Override
        public ServletInputStream getInputStream() {
            return new BufferedServletInputStream(this.buffer, this.is);
        }
    }
}
Copy the code

Create the DispatchConfig class, which is the default configuration to replace Spring Boot,

Different versions of the configuration is different, can be directly copied DispatcherServletAutoConfiguration dispatcherServletRegistration method and the dispatcherServlet method content in class, And replace the DispatcherServlet class for above LoggableDispatcherServlet class

package com.hsh.common.config; import com.hsh.common.dispatch.LoggableDispatcherServlet; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean; import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.MultipartConfigElement; /** * @author lmx 2020/12/30 15:49 */ @Configuration public class DispatchConfig { @Bean @Primary public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName("dispatcherServlet"); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { LoggableDispatcherServlet dispatcherServlet = new LoggableDispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; }}Copy the code

Log example:

2021-01-07 11:44:19.817 INFO 23752 -- [NIO-9083-exec-3] HttpLogger: {"method" : "GET", "url" : "Http://172.16.3.33:9083/api/admin", "remoteAddr" : "172.16.3.33", "x - forwarded - for", null, "request" : {" headers ": {" referer ":" http://172.16.3.33:9410/ ", "accept - language" : "useful - CN, useful; Q = 0.9 ", and "origin", "http://172.16.3.33:9410", "the host" : "172.16.3.33:9083", "connection" : "keep-alive", "x-auth-token" : "2fd100d9-d46a-4a18-8737-c32421cfb491", "accept-encoding" : "gzip, deflate", "accept" : "Application /json, text/plain, */*", "user-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}, "query" : {"current" : [ "1" ], "size" : [ "10" ] } }, "response" : { "status" : 200, "headers" : { "Keep-Alive" : "Timeout =60"," access-Control-allow-origin ": "http://172.16.3.33:9410"," x-Content-type-options ": "nosniff", "Connection" : "keep-alive", "Pragma" : "no-cache", "Date" : "Thu, 07 Jan 2021 03:44:19 GMT", "X-Frame-Options" : "DENY", "Access-Control-Expose-Headers" : "X-Auth-Token", "Cache-Control" : "no-cache, no-store, max-age=0, must-revalidate", "Access-Control-Allow-Credentials" : "true", "Vary" : "Origin", "Expires" : "0", "X-XSS-Protection" : "1; mode=block", "Content-Length" : "907", "Content-Type" : "Application /json"}}} 2021-01-07 11:44:19.818 INFO 23752 -- [NIO-9083-exec-3] HttpLogger: The curl -x 'GET' 'http://172.16.3.33:9083/api/admin? Current =1&size=10&' -h 'host: 172.16.3.33:9083' -h 'connection: keep-alive' -h 'Accept: application/json, text/plain, */*' -H 'x-auth-token: 2fd100d9-d46a-4a18-8737-c32421cfb491' -H 'user-agent: Mozilla / 5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36' -h 'Origin: http://172.16.3.33:9410' -h 'Referer: http://172.16.3.33:9410/' -h 'accept-encoding: gzip, deflate' -H 'accept-language: zh-CN,zh; Q = 0.9 'Copy the code