赖赖的博客

在SpringMVC中打印完整的AccessLog

转载请注明来源 赖赖的博客

导语

总要造一下轮子才知道别人的轮子有多厉害

最近笔者遇到一个问题:在SpringMVC框架下,没有直接的方式可以打印完整的AccessLog,因为request body 和response body是通过inputstream和outputstream来封装的,在使用过很多人提供的方式后,发现效果总是不尽人意,所以自己摸索了一下,完成了这个功能

请慎重在生产环境使用这个功能,因为打印完整的AccessLog是一个很大的消耗

项目工程目录结构和代码获取地址

https://github.com/laiyijie/spring-access-log-filter

详解核心类AccessLogFilter

完整代码如下:

public class AccessLogFilter extends OncePerRequestFilter {
    private static final Logger logger = LogManager.getLogger(AccessLogFilter.class);

    private String usernameKey = "username";
    private Integer payloadMaxLength = 1024;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        Long startTime = System.currentTimeMillis();
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        filterChain.doFilter(requestWrapper, responseWrapper);

        String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
                request.getCharacterEncoding());
        String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
                response.getCharacterEncoding());
        responseWrapper.copyBodyToResponse();
        BLog.accessJsonLogBuilder()
            .addRequestPayLoad(requestPayload)
            .addResponsePayLoad(responsePayload)
            .put(request, usernameKey)
            .put(response)
            .put("_COST_", System.currentTimeMillis() - startTime)
            .log();
    }

    private String getPayLoad(byte[] buf, String characterEncoding) {
        String payload = "";
        if (buf == null) return payload;
        if (buf.length > 0) {
            int length = Math.min(buf.length, getPayloadMaxLength());
            try {
                payload = new String(buf, 0, length, characterEncoding);
            } catch (UnsupportedEncodingException ex) {
                payload = "[unknown]";
            }
        }
        return payload;
    }


    public String getUsernameKey() {
        return usernameKey;
    }

    public void setUsernameKey(String usernameKey) {
        this.usernameKey = usernameKey;
    }

    public Integer getPayloadMaxLength() {
        return payloadMaxLength;
    }

    public void setPayloadMaxLength(Integer payloadMaxLength) {
        this.payloadMaxLength = payloadMaxLength;
    }
}

其核心思想是实现了OncePerRequestFilter这个虚基类,而这个虚基类是实现了Filter接口,并且要基于SpringMVC框架的,因此这个方法只能适用于SpringMVC工程

核心方法是doFilterInternal

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                FilterChain filterChain) throws ServletException, IOException {

    Long startTime = System.currentTimeMillis();
    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

    filterChain.doFilter(requestWrapper, responseWrapper);

    String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
            request.getCharacterEncoding());
    String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
            response.getCharacterEncoding());
    responseWrapper.copyBodyToResponse();
    BLog.accessJsonLogBuilder()
        .addRequestPayLoad(requestPayload)
        .addResponsePayLoad(responsePayload)
        .put(request, usernameKey)
        .put(response)
        .put("_COST_", System.currentTimeMillis() - startTime)
        .log();
}

首先通过

ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

这两个方式对请求和返回进行包装(让body可以被缓存),这样来解决inputstream不能多次读取的问题

调用

filterChain.doFilter(requestWrapper, responseWrapper);

来执行其他的filter之后

可以通过

String requestPayload = getPayLoad(requestWrapper.getContentAsByteArray(),
        request.getCharacterEncoding());
String responsePayload = getPayLoad(responseWrapper.getContentAsByteArray(),
        response.getCharacterEncoding());

这种方式取出request和response的payload,不要忘记

responseWrapper.copyBodyToResponse();

重新写入response

最后打印出整个log