SpringMVC框架中利用Filter实现请求日志打印

SpringMVC框架中利用Filter实现请求日志打印


之前利用HttpServletRequest.getInputStream()和RequestWrapper实现了请求的requestBody获取,现在提出将一个请求的RequestBody和ResponseBody都提出来并打印日志&落入数据库,以便统计和查找问题。查找资料后确定两种技术方案:
1. 使用AOP对所有Controller的方法进行环绕通知处理;
2. 使用Filter拦截所有的Request和Response,并获取body。
最后选择了第二种方式,具体实现记录如下。


具体实现

  1. 日志记录过滤器

    public class RequestFilter implements Filter{
    private static final String LOG_FORMATTER_IN = "请求路径:{%s},请求方法:{%s},参数:{%s},来源IP:{%s},请求开始时间{%s},返回:{%s},请求结束时间{%s},用时:{%s}ms,操作类型:{%s},操作人:{%s}";
    public static final String USER_TOKEN_REDIS_PREFIX = "token_prefix";
    private static final Logger log = LoggerFactory.getLogger(RequestFilter.class);
    //request拦截的conten-type列表
    private List contentTypes;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    
        //请求路径
        String path = httpServletRequest.getRequestURI();
        String method = httpServletRequest.getMethod();
        //所有请求参数的Map
        Map paramMap = new HashMap<>();
        //请求的真实IP
        String requestedIP = RequestUtils.getRealIP(httpServletRequest);
        //是否拦截并包装请求,如果需要拦截则会获取RequestBody,一般为application/json才拦截
        boolean filterRequestFlag = checkFilter(request.getContentType());
        if (filterRequestFlag) {
            httpServletRequest = new MyRequestBodyReaderWrapper(httpServletRequest);
        }
        //获取所有queryString和requestBody
        Map requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest);
        if (requestParamMap != null && !requestParamMap.isEmpty()){
            paramMap.putAll(requestParamMap);
        }
        //获取header参数
        Map headerMap = RequestUtils.getHeaders(httpServletRequest);
        if (headerMap != null && !headerMap.isEmpty()){
           paramMap.putAll(headerMap);
        }
        //获取路径参数
        Map uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest);
        if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){
            paramMap.putAll(uriTemplateMap);
        }
        //包装Response,重写getOutputStream()和getWriter()方法,并用自定义的OutputStream和Writer来拦截和保存ResponseBody
        MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse);
        //请求开始时间
        Long dateStart = System.currentTimeMillis();
        //Spring通过DispatchServlet处理请求
        chain.doFilter(httpServletRequest, responseWrapper);
        //请求结束时间
        Long dateEnd = System.currentTimeMillis();
        String responseBody;
        if (responseWrapper.getMyOutputStream() == null){
                if (responseWrapper.getMyWriter() != null){
                    responseBody = responseWrapper.getMyWriter().getContent();
                    //一定要flush,responseBody会被复用
                    responseWrapper.getMyWriter().myFlush();
                }
            }else {
                responseBody = responseWrapper.getMyOutputStream().getBuffer();
                //一定要flush,responseBody会被复用
                responseWrapper.getMyOutputStream().myFlush();
        }
    
        String params = JSONObject.toJSONString(paramMap);
        log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart));
    }
    
    /**
     * 判断请求/返回是否为application/json
     * 是则进行拦截,
     * 否则退出
     * @param contentType 请求/响应类型
     */
    private boolean checkFilter(String contentType) {
        boolean filterFlag = false;//是否继续拦截
        for (String p : getContentTypes()) {
            if (StringUtils.contains(contentType, p)){
                filterFlag = true;
            }
        }
        if (StringUtils.isEmpty(contentType)){
            filterFlag = true;
        }
        return filterFlag;
    }
    }
    
  2. Request包装器

    /**
    * HttpServletRequest的包装器,为了在拦截器阶段获取requestBody且不妨碍SpringMVC再次获取requestBody
    */
    @Slf4j
    public class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper {
    //存放JSON数据主体
    private final byte[] body;
    public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = getBody(request).getBytes(Charset.forName("UTF-8"));
    }
    
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
    /**
     * 获取请求Body
     */
    public static String getBody(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("MyRequestBodyReaderWrapper.getBody()异常-->",e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("MyRequestBodyReaderWrapper.getBody()异常-->",e);
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("MyRequestBodyReaderWrapper.getBody()异常-->",e);
                }
            }
        }
        return sb.toString();
    }
    }
  3. RequestUtils

    /**
    * 请求工具类
    */
    public class RequestUtils {
    private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);
    /**
     * 获取所有的请求头
     * @param request
     * @return
     */
    public static Map getHeaders(HttpServletRequest request){
        Map headerMap = new HashMap<>();
        List headers = getCommonHeaders();
        headers.add("Postman-Token");
        headers.add("Proxy-Connection");
        headers.add("X-Lantern-Version");
        headers.add("Cookie");
    
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headerName = headerNames.nextElement();
            if (headers.contains(headerName)){
                continue;
            }
            headerMap.put(headerName,request.getHeader(headerName));
        }
        return headerMap;
    }
    
    /**
     * 获取请求的路径参数
     * @param request
     * @return
     */
    public static Map getUriTemplateVar(HttpServletRequest request) {
        NativeWebRequest webRequest = new ServletWebRequest(request);
        Map uriTemplateVars = (Map) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    
        return uriTemplateVars;
    }
    
    /**
     * 获取请求的真实IP
     * @param request
     * @return
     */
    public static String getRealIP(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }
    
    /**
     * 从Request中获取所有的请求参数,包括GET/POST/PATCH等请求,不包括路径参数
     * @param request
     * @return
     */
    public static Map getRequestParamMap(HttpServletRequest request) {
        Map paramMap = new HashMap<>();
    
        //获取QueryString中的参数,GET方式 或application/x-www-form-urlencoded
        Map queryParamMap = RequestUtils.getUriQueryVar(request);
        if (queryParamMap != null){
            paramMap.putAll(queryParamMap);
        }
    
        //获取Body中的参数,POST/PATCH等方式,application/json
        Map bodyParamMap = null;
        try {
            //当为POST请求且 application/json时,request被RequestFilter处理为wrapper类
            if (!(request instanceof MyRequestBodyReaderWrapper)){
                return paramMap;
            }
            MyRequestBodyReaderWrapper readerWrapper = (MyRequestBodyReaderWrapper) request;
            String requestBody = new String(readerWrapper.getBody(), "UTF-8");
            if (com.zhongan.health.common.utils.StringUtils.isNotBlank(requestBody)){
                /**
                 * 该方法为了避免 fastJson在 反序列化多层json时,改变对象顺序
                 */
                bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference>(){}, Feature.OrderedField);
            }
        } catch (Exception e) {
            logger.error("获取请求Body异常-->",e);
        }
    
        if (bodyParamMap != null){
            paramMap.putAll(bodyParamMap);
        }
    
        return paramMap;
    }
    
    private static List getCommonHeaders(){
        List headers = new ArrayList<>();
        Class clazz = HttpHeaders.class;
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getType().toString().endsWith("java.lang.String") && Modifier.isStatic(field.getModifiers())){
                try {
                    headers.add((String) field.get(HttpHeaders.class));
                } catch (IllegalAccessException e) {
                    logger.error("反射获取属性值异常-->",e);
                }
            }
        }
        return headers;
    }
    }
  4. Response包装器

    /**
    *该包装器主要是重写getOutputStream()和getWriter()方法,给调用者返回自定义的OutputStream和Writer,以便参与输出的过程并记录保存responseBody。
    */
    public class MyResponseWrapper extends HttpServletResponseWrapper {
    private ResponsePrintWriter writer;
    private MyServletOutputStream out;
    public MyResponseWrapper(HttpServletResponse response) {
        super(response);
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        //一定要先判断当前out为空才能去新建out对象,否则一次请求会出现多个out对象
        if (out == null){
            out = new MyServletOutputStream(super.getOutputStream());
        }
        return out;
    }
    @Override
    public PrintWriter getWriter() throws IOException {
        //一定要先判断当前writer为空才能去新建writer对象,否则一次请求会出现多个writer对象
        if (writer == null){
            writer = new ResponsePrintWriter(super.getWriter());
        }
        return writer;
    }
    public ResponsePrintWriter getMyWriter() {
        return writer;
    }
    public MyServletOutputStream getMyOutputStream(){
        return out;
    }
    }
  5. 自定义Writer

    /**
    *自定义Writer,重写write方法,并记录保存ResponseBody
    */
    public class ResponsePrintWriter extends PrintWriter{
    private StringBuffer buffer;
    public ResponsePrintWriter(PrintWriter out) {
        super(out);
        buffer = new StringBuffer();
    }
    public String getContent(){
        return buffer == null ? null : buffer.toString();
    }
    @Override
    public void flush() {
        super.flush();
    }
    //清空buffer,以便下一次重新使用
    public void myFlush(){
        buffer = null;
    }
    @Override
    public void write(char[] buf, int off, int len) {
        super.write(buf, off, len);
        char[] destination = new char[len];
        System.arraycopy(buf,off,destination,0,len);
        buffer.append(destination);
    }
    @Override
    public void write(String s) {
        super.write(s);
        buffer.append(s);
    }
    }
  6. 自定义OutputStream

    /**
    * 自定义输出流包装器,重写write方法,并记录保存ResponseBody
    */
    public class MyServletOutputStream extends ServletOutputStream {
    private ServletOutputStream outputStream;
    private StringBuffer buffer;
    public MyServletOutputStream(ServletOutputStream outputStream) {
        this.outputStream = outputStream;
        buffer = new StringBuffer();
    }
    @Override
    public void write(int b) throws IOException {
        outputStream.write(b);
    }
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        outputStream.write(b, off, len);
        byte[] bytes = new byte[len];
        System.arraycopy(b, off, bytes, 0, len);
        buffer.append(new String(bytes,"UTF-8"));
    }
    @Override
    public void write(byte[] b) throws IOException {
        outputStream.write(b);
    }
    @Override
    public void flush() throws IOException {
        super.flush();
    }
    //清空buffer,以便下一次重新使用
    public void myFlush(){
        outputStream = null;
        buffer = null;
    }
    public String getBuffer() {
        if (buffer != null){
            return buffer.toString();
        }
        return null;
    }
    }
    

总结

  1. Request.getInputStream一次请求中只能被调用一次;
  2. Response.getOutputStream()无法获取ResponseBody;
  3. Response的输出有两种方式,都需要考虑到并重写
    • getOutputStream().write()
    • getWrite().write()

你可能感兴趣的:(JAVA相关笔记,系统架构和优化)