拿来吧你——一个类帮你搞定SpringBoot中的请求日志打印

拿来吧你——一个类帮你搞定SpringBoot中的请求日志打印

敲得码黛 440 2023-05-06

日常开发工作中避免不了要打印请求日志,这个功能几乎在所有的项目中都需要编写一次,重复的次数多了,难免会感觉繁琐,因此打算搞一个通用类把这块功能拆出来。

废话不多说——先上代码。

我是代码

@WebFilter(filterName = "logFilter", urlPatterns = "/planet/*")
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class LogFilter implements Filter {

    private static final Set<String> IGNORE_PATHS = Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList("/**/swagger-ui/**", "/**/swagger-resources/**", "/**/api-docs")));

    @SneakyThrows
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        // 忽略文件上传
        String contentType = httpServletRequest.getHeader("content-type");
        if (contentType != null && contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        // 忽略指定url
        String path = httpServletRequest.getRequestURI().substring(httpServletRequest.getContextPath().length()).replaceAll("[/]+$", "");
        PathMatcher matcher = new AntPathMatcher();
        boolean isIgnore = IGNORE_PATHS.stream().anyMatch(ignore -> matcher.match(ignore, path));
        if (isIgnore) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        ResponseWrapper wrapperResponse = new ResponseWrapper(httpServletResponse);
        RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
        long before = System.currentTimeMillis();

        log.info("========================================== Request Start ==========================================");
        log.info("URL            : {}", httpServletRequest.getRequestURL().toString());

        Map<String, String> headers = new HashMap<>(32);
        Enumeration<String> names = httpServletRequest.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            headers.put(name, httpServletRequest.getHeader(name));
        }
        log.info("Headers            : {}", headers);
        log.info("HTTP Method    : {}", httpServletRequest.getMethod());
        log.info("IP             : {}", httpServletRequest.getRemoteAddr());
        log.info("Request Body   : {}", JSON.toJSON(requestWrapper.getBody()));
        filterChain.doFilter(requestWrapper, wrapperResponse);
        byte[] content = wrapperResponse.getContent();
        log.info("Response Status  : {}", wrapperResponse.getStatus());
        log.info("Response Content  : {}", JSON.toJSON(new String(content)));
        log.info("Time-Consuming : {} ms", System.currentTimeMillis() - before);
        log.info("=========================================== End ===========================================");
        ServletOutputStream out = httpServletResponse.getOutputStream();
        out.write(content);
        out.flush();
    }
}


class ResponseWrapper extends HttpServletResponseWrapper {

    private final ByteArrayOutputStream buffer;

    private final ServletOutputStream out;

    public ResponseWrapper(HttpServletResponse httpServletResponse) {
        super(httpServletResponse);
        buffer = new ByteArrayOutputStream();
        out = new WrapperOutputStream(buffer);
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return out;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
    }

    public byte[] getContent() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }

    static class WrapperOutputStream extends ServletOutputStream {
        private final ByteArrayOutputStream bos;

        public WrapperOutputStream(ByteArrayOutputStream bos) {
            this.bos = bos;
        }

        @Override
        public void write(int b) {
            bos.write(b);
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener arg0) {

        }
    }

}

class RequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ignored) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

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

    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }

}

相关问题

这个类是干啥用的?

这个类是一个过滤器,用来在Request与Response之间打印请求与响应日志。

tip:一次编写、处处粘贴。

这个类的处理逻辑是啥?

众所周知,Servlet用来处理用户请求并且对用户请求进行响应,而Filter则是在请求达到Servlet之前,服务器响应返回到Servlet之前对数据进行的一次预处理。

里面几个内部类是干啥用的?

因为request、response都是以流的形式进行传输的,而流有一个特性就是只允许读取一次,因此需要对Request与Response进行一次扩展,使其支持多次读取,其实现方式为:在第一次读取的时候将数据缓存起来,后续读取时直接读取缓存的内容。

还有其他问题吗?

过滤器虽然功能比较强大,但是其影响面也是十分巨大的。

因为过滤器会在所有的请求前后做一些预处理操作,如果预处理操作比较耗时则会降低部分系统性能(QPS、TPS)。

Ctrl+C之前,记得先点个赞哦。