Two ways of request parameters and response results of springboot logging interface - interceptor and aspect (specific code)

Two ways of request parameters and response results of springboot logging interface - interceptor and aspect (specific code)

Preface: If there is a problem in production, we want to check the log, the request parameters of the user's call interface and the return result of the response in a certain period of time, and use the log to guess what the user did at that time. Logging the request parameters and response results of the interface is helpful for us to troubleshoot production problems, but it will also bring memory performance problems to the system. So we need to weigh the pros and cons to choose. The following is the specific code for the two methods of logging.

1. Use facets (recommended to use this, simple)
@Component
@Aspect
@Slf4j
public class ApiLogAspect {
    
    

    @Pointcut("execution(* com.xl.finance.module..controller..*.*(..))")
    public void controller() {
    
    
    }

    @Around("controller()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
    
    
        long startTime = System.currentTimeMillis();
        Long userId = WebUtils.getId();
        String userName = WebUtils.getName();
        String requestUrl = WebUtils.getRequestUrl();
        requestUrl = new URL(requestUrl).getPath();
        String requestParam = getMethodArgs(point);
        Object result = point.proceed();
        long endTime = System.currentTimeMillis();
        String responseParam = handlerResult(result);
        log.info("requestUrl:{} userId:{},userName:{} requestParam:{},responseParam:{},rtt:{}ms", requestUrl, userId, userName, requestParam, responseParam, endTime - startTime);
        return result;
    }

    private String getMethodArgs(JoinPoint point) {
    
    
        Object[] args = point.getArgs();
        if (args == null || args.length == 0) {
    
    
            return "";
        }
        try {
    
    
            Map<String, Object> params = new HashMap<>();
            String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames();
            for (int i = 0; i < parameterNames.length; i++) {
    
    
                Object arg = args[i];
                // 过滤不能转换成JSON的参数
                if ((arg instanceof ServletRequest) || (arg instanceof ServletResponse)) {
    
    
                    continue;
                } else if ((arg instanceof MultipartFile)) {
    
    
                    arg = arg.toString();
                }
                params.put(parameterNames[i], arg);
            }
            return JSONObject.toJSONString(params);
        } catch (Exception e) {
    
    
            log.error("接口出入参日志打印切面处理请求参数异常", e);
        }
        return Arrays.toString(args);
    }

    /**
     * 返回结果简单处理
     * 1)把返回结果转成String,方便输出。
     * 2)返回结果太长则截取(最多3072个字符),方便展示。
     *
     * @param result 原方法调用的返回结果
     * @return 处理后的
     */
    private String handlerResult(Object result) {
    
    
        if (result == null) {
    
    
            return null;
        }
        String resultStr;
        try {
    
    
            if (result instanceof String) {
    
    
                resultStr = (String) result;
            } else {
    
    
                resultStr = JSONObject.toJSONString(result);// 如果返回结果非String类型,转换成JSON格式的字符串
            }

            if (resultStr.length() > 3072) {
    
    
                resultStr = resultStr.substring(0, 3072);
            }
        } catch (Exception e) {
    
    
            resultStr = result.toString();
            log.error("接口出入参日志打印切面处理返回参数异常", e);
        }
        return resultStr;
    }
}
2. Use interceptors

Originally thought that in this way, as long as the custom interceptor inherits the HandlerInterceptorAdapter class and rewrites preHandle() and afterCompletion(), it will be fine. I didn't expect to encounter a lot of pitfalls.

Request parameters: request.getParameterMap() can get the request parameters, but if the interface uses @RequestBody, you will find that you cannot get the value.

Response result: This is a bit painful. I checked almost all the methods of response, but found that I could not get the interface response result.

Paste the code directly below.

2.1 Implement the Filter class

The flow is one-time, in order to prevent the request parameters of the flow from being read in the log, affecting the interface of the actual request

@Component
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/")
@Order(10000)
public class HttpServletRequestFilter implements Filter {
    
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    
    
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    
        ServletRequest requestWrapper = null;
        if(servletRequest instanceof HttpServletRequest) {
    
    
            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中
        // 在chain.doFiler方法中传递新的request对象
        if(null == requestWrapper) {
    
    
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
    
    
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }

    @Override
    public void destroy() {
    
    
        Filter.super.destroy();
    }
}
2.2 Inherit the RequestWrapper class
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
    
    

    private final String body;

    public RequestWrapper(HttpServletRequest request) {
    
    
        super(request);

        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try (InputStream inputStream = request.getInputStream()) {
    
    
            if(inputStream != null){
    
    
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
    
    
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
    
    
            log.error("RequestWrapper read error :{}",e.getMessage());
        } finally {
    
    
            IoUtil.close(bufferedReader);
        }
        body = stringBuilder.toString();
    }

    /**
     * 将getInputStream重新,让它能重复获取到body里的内容,这样才不会影响后续的流程
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
    
    
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
    
    
            @Override
            public boolean isFinished() {
    
    
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
    
    
            }

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

    /**
     * 重写获取 字符流的方式
     * @return
     */
    @Override
    public BufferedReader getReader() throws IOException {
    
    
        return new BufferedReader(new InputStreamReader(this.getInputStream(), StandardCharsets.UTF_8));
    }


    /**
     * 获取body
     * @return
     */
    public String getBody() {
    
    
        return this.body;
    }
}
2.3 Implement the ResponseBodyAdvice class

After implementing ResponseBodyAdvice, you can get the result of the interface response to print the log, but in order to process the log uniformly in the custom interceptor, here the response result is stored in an attribute of response.

@ControllerAdvice
public class InterceptResponse implements ResponseBodyAdvice<Object> {
    
    

    private static final Integer MAX_LENGTH = 1000;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    
    
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
    
    
        String result ;
        if(body instanceof JsonResult){
    
    
            JsonResult jsonResult = (JsonResult) body;
            result = JSON.toJSONString(jsonResult);
        }else{
    
    
            result = JSON.toJSONString(body);
        }
        Integer length = result.length() > MAX_LENGTH ? MAX_LENGTH :  result.length();
        result = result.substring(0,length);
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession httpSession = httpServletRequest.getSession(true);
        //放到缓存里,以便于可以在HandlerInterceptor拦截里取出并打印出返回结果
        httpSession.setAttribute("body", result);
        return body;
    }
}
2.4 Newly defined interceptor inherits HandlerInterceptorAdapter
@Component
@Slf4j
public class LogMonitorInterceptor extends HandlerInterceptorAdapter {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        String requestParams = StringUtils.EMPTY;
        if (request instanceof RequestWrapper) {
    
    
            requestParams = ((RequestWrapper) request).getBody();
        }
        if(StrUtil.isEmpty(requestParams)){
    
    
            requestParams = JSON.toJSONString(request.getParameterMap());
        }
        log.info("request:  uri:{} , type:{} , ip:{}, operatorId:{}, operatorName:{}, params:{}",
                request.getRequestURI(),request.getMethod(),request.getRemoteAddr(),
                WebUtils.getId(),WebUtils.getUsername(), requestParams );
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        HttpSession httpSession = request.getSession();
        String result = (String) httpSession.getAttribute("body");

        log.info("response:  url:{} , type:{} , ip:{}, operatorId:{}, operatorName:{}, result:{}",
                request.getRequestURI(),request.getMethod(),request.getRemoteAddr(),
                WebUtils.getId(),WebUtils.getUsername(), result );
    }
}
2.5 Implement WebMvcConfigurer
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    
    @Autowired
    private LogMonitorInterceptor logMonitorInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    

        // 接口操作日志拦截器
        registry.addInterceptor(logMonitorInterceptor);
    }
}

Guess you like

Origin blog.csdn.net/qq798867485/article/details/129861674