zoukankan      html  css  js  c++  java
  • SpringBoot 记录HTTP请求日志,curl格式,可直接运行于bash,也可导入postman

    新建LoggableDispatcherServlet 类:

    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("'");
                    String url = stringBuilder.toString();
                    if (url.endsWith("&'")) {
                        url = url.substring(0, url.length() - 2) + "'";
                    }
                    curlItemList.add(url);
                    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("&");
                            }
                        }
                        String paramsStr = stringBuilder.toString();
                        if (paramsStr.endsWith("&")) {
                            paramsStr = paramsStr.substring(0, paramsStr.length() - 1);
                        }
                        curlItemList.add(MessageFormat.format("--data-raw ''{0}''", paramsStr));
                        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, "
       "));
            }
        }
    
        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);
            }
        }
    }
    View Code

    新建DispatchConfig类,这个配置类是替换spring boot的默认配置,

    不同版本的配置有所不同,可直接复制DispatcherServletAutoConfiguration类中dispatcherServletRegistration方法和dispatcherServlet方法的内容,并替换DispatcherServlet类为上文的LoggableDispatcherServlet类

    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;
        }
    
    }
    View Code

    日志示例:

    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" : "zh-CN,zh;q=0.9",
          "origin" : "http://172.16.3.33:9410",
          "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                               : 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'

    参考文献:

    SpringBoot 超详细的记录HTTP请求日志

  • 相关阅读:
    Remoting系列(一)Remoting的基本概念
    软件设计师
    如果让我重做一次研究生
    VS2005Web控件拖动
    JS实现文本框回车提交
    SqlDataReader
    SqlCommand.ExecuteScalar
    DataSet
    电子商务部应该做些什么?【转】
    e
  • 原文地址:https://www.cnblogs.com/buyiblogs/p/14245851.html
Copyright © 2011-2022 走看看