美文网首页
SpringBoot2.x 利用Filter打印pv日志

SpringBoot2.x 利用Filter打印pv日志

作者: 小胖学编程 | 来源:发表于2021-11-04 22:05 被阅读0次

ELK日志收集时,我们需要在Filter中统一的打印请求报文。便于在Kibina中查询

遇见两个问题:

  1. Servlet流是单向的,在Filter中解析那么后续会出现异常;
  2. 对于请求的不同Method(GET/POST等)如何在Filter中解析为字符串;

解决方案:

1.1 将单向流设置为可重复读流

定义一个优先级高的Filter,实现流的转换。

@Order(Integer.MIN_VALUE + 1)
@Slf4j
@Service
public class RepeatableFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;

        String contentType = req.getContentType();
        //对于文件上传,直接放行。
        if (contentType != null && contentType.toLowerCase().contains("multipart")) {
            //直接放行
            chain.doFilter(request, response);
            return;
        }
        //重写流对象
        if (!(request instanceof BackupRequestWrapper)) {
            req = new BackupRequestWrapper(req);
        }
        chain.doFilter(req, response);
    }
}

原理是:继承HttpServletRequestWrapper,内部使用ByteArrayInputStream来实现流的可重复读。

public class BackupRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] buffer;

    public BackupRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream is = request.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int read;
        byte[] buff = new byte[1024];
        while ((read = is.read(buff)) > 0) {
            baos.write(buff, 0, read);
        }
        this.buffer = baos.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new BufferedServletInputStream(this.buffer);
    }

    // 对外提供读取流的方法
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

class BufferedServletInputStream extends ServletInputStream {
    private final ByteArrayInputStream inputStream;

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

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

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

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

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

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

    @Override
    public void setReadListener(ReadListener readListener) {

    }
}

1.2 将request参数读取为JSON

提供一个工具类:来针对GET/POST请求来单独处理。

@Slf4j
public class RequestJsonUtil {


    /***
     * 获取 request 中 json 字符串的内容
     *
     * @return : <code>byte[]</code>
     * @throws IOException
     */
    public static String getRequestJsonString(HttpServletRequest request) throws IOException {
        String submitMehtod = request.getMethod();
        // GET
        String s = new String("".getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8).replaceAll("%22", "\"");
        if (submitMehtod.equals("GET")) {
            if (StringUtils.isNotEmpty(request.getQueryString())) {
                return new String(request.getQueryString().getBytes(StandardCharsets.ISO_8859_1),
                        StandardCharsets.UTF_8).replaceAll("%22", "\"");
            }
            return s;
        }


        // POST
        String requestString = getRequestPostStr(request);

        if (StringUtils.isNotBlank(requestString)) {
            return requestString;
        }

        if (StringUtils.isNotEmpty(request.getQueryString())) {
            return new String(request.getQueryString().getBytes(StandardCharsets.ISO_8859_1),
                    StandardCharsets.UTF_8).replaceAll("%22", "\"");
        }
        return s;
    }

    public static String getRequestJsonStringNoException(HttpServletRequest request) {
        try {
            return getRequestJsonString(request);
        } catch (IOException e) {
            log.error("获取请求参数异常", e);
        }
        return null;
    }

    /**
     * 描述:获取 post 请求的 byte[] 数组
     * <pre>
     * 举例:
     * </pre>
     */
    public static byte[] getRequestPostBytes(HttpServletRequest request)
            throws IOException {
        int contentLength = request.getContentLength();
        if (contentLength < 0) {
            return null;
        }
        byte[] buffer;
        buffer = new byte[contentLength];
        int i = 0;
        while (i < contentLength) {
            int readlen = request.getInputStream().read(buffer, i,
                    contentLength - i);
            if (readlen == -1) {
                break;
            }
            i += readlen;
        }
        return buffer;
    }

    /**
     * 描述:获取 post 请求内容
     * <pre>
     * 举例:
     * </pre>
     */
    public static String getRequestPostStr(HttpServletRequest request)
            throws IOException {
        byte[] buffer = getRequestPostBytes(request);
        String charEncoding = request.getCharacterEncoding();
        if (charEncoding == null) {
            charEncoding = "UTF-8";
        }
        if (null == buffer) {
            return null;
        } else {
            return new String(buffer, charEncoding);
        }
    }

}

然后在利用Filter打印数据,然后配置logback.xml等日志配置文件来将这个pv Filter类的日志输出打印到一个单独日志文件中。使用Elk来解析收集展示pv日志。

相关文章

网友评论

      本文标题:SpringBoot2.x 利用Filter打印pv日志

      本文链接:https://www.haomeiwen.com/subject/xqhkzltx.html