欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

打印HTTP request body和response body实现日志跟踪

程序员文章站 2022-07-12 11:56:49
...

request 的inputStream和response 的outputStream默认情况下是只能读一次, 不可重复读;这就导致要获取请求体或者响应体信息时必须要声明包裹类wrapper; spring为此提供了两个对应的包裹类ContentCachingRequestWrapper和ContentCachingResponseWrapper使得这两个流信息可重复读;

可以利用这两个类结合spring的WebUtil工具类来实现日志跟踪:

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
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.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.UUID;

/**
 * @author: liumengbing
 * @date: 2019/03/12 10:10
 **/
@Component
@Slf4j
@WebFilter("/*")
public class WebLogFilter extends OncePerRequestFilter {

    private static Logger log = LoggerFactory.getLogger(WebLogFilter.class);

    public static final String SPLIT_STRING_M = "=";

    public static final String SPLIT_STRING_DOT = ", ";


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //日志trade
        MDC.clear();
        //使用唯一ID标识每次请求
        MDC.put("trade_id", UUID.randomUUID().toString().replaceAll("-",""));
        ContentCachingRequestWrapper wrapperRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrapperResponse = new ContentCachingResponseWrapper(response);

        String urlParams = getRequestParams(request);
        log.info("request params[{}]", urlParams);
        filterChain.doFilter(wrapperRequest, wrapperResponse);

        String requestBodyStr = getRequestBody(wrapperRequest);
        log.info("request body:{}", requestBodyStr);

        String responseBodyStr = getResponseBody(wrapperResponse);

        log.info("response body:{}", responseBodyStr);
        wrapperResponse.copyBodyToResponse();
    }

    /**
     * 获取请求地址上的参数
     * @param request
     * @return
     */
    public static String getRequestParams(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        Enumeration<String> enu = request.getParameterNames();
        //获取请求参数
        while (enu.hasMoreElements()) {
            String name = enu.nextElement();
            sb.append(name + SPLIT_STRING_M).append(request.getParameter(name));
            if(enu.hasMoreElements()) {
                sb.append(SPLIT_STRING_DOT);
            }
        }
        return sb.toString();
    }

    /**
     * 打印请求参数
     * @param request
     */
    private String getRequestBody(ContentCachingRequestWrapper request) {
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if(wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if(buf.length > 0) {
                String payload;
                try {
                    payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                } catch (UnsupportedEncodingException e) {
                    payload = "[unknown]";
                }
                return payload.replaceAll("\\n","");
            }
        }
        return "";
    }

    /**
     * 打印返回参数
     * @param response
     */
    private String getResponseBody(ContentCachingResponseWrapper response) {
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if(wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if(buf.length > 0) {
                String payload;
                try {
                    payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                } catch (UnsupportedEncodingException e) {
                    payload = "[unknown]";
                }
                return payload;
            }
        }
        return "";
    }

}

在以上代码中还有一个技术点:使用了slf4j的MDC来跟踪请求信息,打印完整的请求处理日志;并在日志配置中加上了唯一ID,从而达到日志追踪的目的。

日志配置文件:

[%X{trade_id}]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

输出的日志信息:

[16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.127  INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter       : request params[{}]:account=xxxxx,  v_code=056036}
[16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.130  INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter       : request body:{}:{    "account":"xxxxx",  "v_code":"056036"}
[16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.131  INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter       : response body:{}:{"code":1,"msg":"请输入正确的验证码","data":"","success":false}