springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码)

这篇具有很好参考价值的文章主要介绍了springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码)

前言:在生产中如果出现问题,我们想要查看日志,某个时间段用户调用接口的请求参数和响应的返回结果,通过日志来推测下用户当时做了什么操作。日志记录接口的请求参数和响应结果有利于我们排查生产的问题,但是也会给系统带来内存性能的问题。所以我们需要权衡其中的利弊来选择,下面就是记录日志两种方式的具体代码。

一、使用切面(推荐使用这种,简单)
@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;
    }
}
二、使用拦截器

本来以为这种方式只要自定义拦截器继承HandlerInterceptorAdapter类重写preHandle() 和 afterCompletion()就可以了。没想到还是遇到挺多坑。

请求参数:request.getParameterMap()可以获取到请求参数,但是如果接口是使用@RequestBody,就会发现得不到值。

响应结果:这是比较蛋疼一点,我几乎查了response的所有方法,都发现没法得到接口响应结果。

下面直接贴代码。

2.1 实现Filter类

流是一次性的,为了防止在日志中读取到流的请求参数,影响实际请求的接口

@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 继承RequestWrapper类
@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 实现ResponseBodyAdvice类

在实现ResponseBodyAdvice,就可以获取到接口响应的结果来打印日志,但是为了统一在自定义的拦截器处理日志,这里把响应的结果存在response的一个属性里。文章来源地址https://www.toymoban.com/news/detail-785741.html

@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 新建定义拦截器继承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 实现WebMvcConfigurer
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private LogMonitorInterceptor logMonitorInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

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

到了这里,关于springboot 日志记录接口的请求参数和响应结果的两种方式-拦截器和切面(具体代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Springboot调整接口响应返回时长详解(解决响应超时问题)_springboot设置请求超时时间

    1、配置Http会话超时 可以通过两种方式为Spring Boot应用程序 配置HTTP会话超时 。 1.1 application.properties中配置会话超时 最简单的方法是在你的application.properties中加入参数 server.servlet.session.timeout 。 还要注意的是, Tomcat不允许你将超时时间设置得少于60秒 。 1.2 以程序方式配置会

    2024年04月27日
    浏览(67)
  • python requests.get发送Http请求响应结果乱码、Postman请求结果正常

    最近在写爬虫程序,自己复制网页http请求的url、头部,使用python requests和postman分别请求,结果使用postman发送http get请求,可以得到正常的json数据,但是使用python的requests发送则接受到乱码,response.text的内容是: response.content的内容是: 十分费解,于是网上搜索了相关内容,

    2024年01月24日
    浏览(54)
  • SpringBoot初级开发--服务请求(GET/POST)所有参数的记录管理(8)

      服务端在定位错误的时候,有时候要还原现场,这就要把当时的所有入参参数都能记录下来,GET还好说,基本NGINX都会记录。但是POST的请求参数基本不会被记录,这就需要我们通过一些小技巧来记录这些参数,放入日志,这里我们通过自定义拦截器来做这个操作。 我们紧

    2024年02月09日
    浏览(43)
  • Spring/SpringBoot 过滤器修改、获取http 请求request中的参数 和 response返回值,比如修改请求体和响应体的字符编码

    通过自定义filter,RequestWrapper,ResponseWrapper 处理请求和响应数据,比如修改请求体和响应体的字符编码 1.request 和 response 中的数据都是 存在流中的(缓存中)获取一次就没有了,需要重新写回去。所以需要两个包装类分别继承HttpServletRequestWrapper 和 HttpServletResponseWrapper 对 r

    2024年02月15日
    浏览(39)
  • Python发送Post请求及解析响应结果

    有时候遇到请求url中有很多参数。 1.1 示例1 accounts和pwd请到http://shop-xo.hctestedu.com/注册。 执行结果: 1.2 示例2 使用不定长参数 params,将url中需要的参数单独封装。 执行结果: 用type()查看response.text的类型,是str 执行结果: 用type()查看response.json()的类型,是dict 执行结果:

    2023年04月24日
    浏览(84)
  • postman获取请求响应结果并设置到全局变量中

    做接口测试中,经常遇到就是我们首先要去获取一个请求响应返回的参数(这个返回值是我们需要的),这个接口我们跑通了返回值也有了,那么如何去将它提取出来并写入到全局变量里去呢? 可通过返回值的层级一步一步的获取到想要的返回值并保存到变量里面,如下:

    2024年02月15日
    浏览(60)
  • SpringCloud Gateway 打印请求响应日志

    version spring-cloud 2021.0.1 spring-boot 2.6.3 spring-cloud-alibaba 2021.0.1.0 网关不是基于springmvc的,而是基于webflux去做的 SpringCloudGateway中Post请求参数只能读取一次 这是因为Gateway默认使用的是SpringWebflux,解决这个问题需要容重新构造一个request来替换原先的request CacheBodyGlobalFilter这个全局过

    2024年02月02日
    浏览(49)
  • 【Postman】批量请求接口并存储返回结果

    摘要: 这是一篇0基础工具文档 使用:postman 参数化、测试断言、存储测试结果 等几项功能 实现:对接口进行批量请求,并存储结果 先创建一个集合,再在集合中创建接口请求 这样执行集合的时候,就可以通过参数化的文本文件,实现对同一接口的批量执行 贴图: 如图输

    2024年01月21日
    浏览(42)
  • 鸿蒙开发,使用http返回的响应数据无法正常获取 ,利用hilog打印日志一直结果是object或者代码凭空消失,根本没有打印日志(灵异事件???)

    这里简述项目相关背景:前后端分离项目,使用鸿蒙做前端,后端SpringBoot写好接口(通过商品分类id查询商品列表),鸿蒙前端页面使用Tabs组件导航,展示商品分类,点击分类标签,查询后端接口,返回对应分类商品列表数据 项目场景:鸿蒙开发,使用http返回的响应数据无

    2024年04月27日
    浏览(41)
  • 请求响应-实体参数的接受

    简单实体对象: 请求参数名与形参属性对象名相同,定义pojo接受即可,将数据封装到实体类中 实体类代码如下: 控制类代码如下:    postman中发送请求、idea接受并处理请求结果如下: 复杂实体参数接受 (一个实体类中包含另一个实体类作为对象,即 对象的组合 ) 参数

    2024年02月16日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包