一、统计接口耗时

通过日志记录接口耗时情况对于性能监控、故障排查等方面具有非常重要意义。而实现这一功能的方式多种多样,包括使用AOP、拦截器、日志框架以及性能监控工具等。下面是对这几种实现方式的优缺点进行总结:

1.AOP

优点:代码解耦,复用性强,易于维护。 

缺点:可能增加系统复杂度,性能略有损耗(毕竟要生成代理)。

2.性能监控工具

优点:功能全面,可视化强,易于分析。 

缺点:可能引入外部依赖,成本较高。

3.拦截器

优点:与Spring MVC等框架集成好,实现简单。

缺点:只能用于Web层,不能跨应用层。

我们将使用SpringBoot自带的一种简单且强大的实现方式来帮助我们高效地监控接口性能,当请求到达Servlet处理程序时,Spring就开始记录当前请求的耗时情况了,当请求处理完成后,会以事件的方式发布当前请求详细信息。

示例代码如下:

package com.example.dataproject.listener;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.ServletRequestHandledEvent;

/**
 * @author qx
 * @date 2024/8/9
 * @des 统计耗时监听
 */
@Component
public class TimeConsumeListener implements ApplicationListener<ServletRequestHandledEvent> {
    @Override
    public void onApplicationEvent(ServletRequestHandledEvent event) {
        System.err.println("**************************************");
        System.err.printf("请求客户端地址:%s\n请求URL:%s\n请求Method:%s\n请求耗时:%d毫秒\n", event.getClientAddress(), event.getRequestUrl(),
                event.getMethod(), event.getProcessingTimeMillis());
        System.err.println("**************************************");
    }
}

创建一个控制层

package com.example.dataproject.controller;

import com.example.dataproject.entity.MyData;
import com.example.dataproject.service.MyDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author qx
 * @date 2024/8/1
 * @des 控制层
 */
@RestController
public class MyDataController {

    @Autowired
    private MyDataService myDataService;

    /**
     * 批量插入数据
     */
    @PostMapping("/batchInsert")
    public String batchInsertData(@RequestBody List<MyData> dataList) {
        myDataService.batchInsertData(dataList);
        return "batchInsert success";
    }

    /**
     * 生成数据
     */
    @GetMapping("/generateData")
    public List<MyData> generateData() {
        List<MyData> dataList = new ArrayList<>();
        Random random = new Random();
        MyData myData;
        for (int i = 0; i < 1000; i++) {
            myData = new MyData();
            myData.setName("name:" + i);
            myData.setDescription("desc:" + random.nextInt());
            dataList.add(myData);
        }
        return dataList;
    }

}

启动程序,调用接口访问

SpringBoot的几个高级技能的学习_404

我们查看控制台上打印出了请求接口相关的耗时信息。

SpringBoot的几个高级技能的学习_404_02

二、Controller接口调试

对Controller接口的请求参数、请求Header和响应结果进行日志输出是非常必要的。以下是一些常见的情况:

  • 问题排查:当接口出现错误或异常行为时,通过查看日志中的请求参数和Header信息,可以快速定位问题发生的上下文,比如是否传入了错误的参数、缺失了必要的Header等。同时,响应结果的日志也能帮助确认后端处理逻辑是否正确执行,以及错误是否由后端逻辑导致。
  • 安全审计:在涉及敏感数据或高安全要求的系统中,记录请求和响应的详细信息有助于进行安全审计。通过检查日志,可以追溯敏感数据的流向,识别潜在的危险行为。
  • 监控与报警:将日志信息集成到监控系统中,可以实时监控接口的运行状态。当接口出现异常或性能指标超出阈值时,系统可以自动触发报警,及时通知相关人员进行处理。

其实在大多数情况下,我们进行详细信息的输出都是为了问题的排查。至于安全审计,监控与报警通过日志的方式应该是应用的非常少的。

在SpringBoot项目中,为了输出详细的接口请求信息,我们可以通过如下的方式进行日志级别调整。

logging.level.web=TRACE

示例代码如下:

package com.example.dataproject.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2024/8/9
 * @des
 */
@RestController
public class DemoController {

    @GetMapping("/index")
    public Object index(Long id, String msg) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("x-version", "1.0.0");
        ResponseEntity<Object> response = new ResponseEntity<Object>(
                String.format("【id = %d, msg = %s】", id, msg),
                headers,
                HttpStatus.OK.value());
        return response;
    }
}

请求该接口,日志输出

SpringBoot的几个高级技能的学习_耗时统计_03

针对该接口确实输出的信息比较多了,有请求的有响应,但是发现关键信息都被掩码了,这对我们调试接口没有任何的帮助。通过如下配置,开启请求明细信息输出。

spring.mvc.log-request-details=true

开启该属性后,日志输出

SpringBoot的几个高级技能的学习_接口调试_04

关键的信息都输出了,这非常有助于接口调试,生产环境相信你绝对不可能这样输出日志的。

三、404问题

当我们访问一个不存在的接口时,默认情况下会输出如下错误信息(这会根据你当前请求header中的accept来决定输出格式)

浏览器访问不存在的接口。

SpringBoot的几个高级技能的学习_404_05

我们需要在配置文件添加2个配置

# 如果没有找到请求地址,抛异常
spring.mvc.throw-exception-if-no-handler-found=true
# 关闭默认的静态资源路径映射
spring.web.resources.add-mappings=false

设置一个全局异常处理类

package com.example.dataproject.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qx
 * @date 2024/8/8
 * @des 全局异常处理类
 */
@RestControllerAdvice
@Slf4j
public class GlobalException {


    @ExceptionHandler(value = {Exception.class})
    public Map<String, Object> exceptionHandler(HttpServletRequest request, Exception e) {
        log.info("未知异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", 999);
        map.put("message", e.getMessage());
        return map;
    }

    @ExceptionHandler(value = {ServiceException.class})
    public Map<String, Object> serviceExceptionHandler(HttpServletRequest request, ServiceException e) {
        log.info("自定义异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("message", e.getMessage());
        return map;
    }

    @ExceptionHandler(value = {NullPointerException.class})
    public Map<String, Object> nullPointExceptionHandler(HttpServletRequest request, NullPointerException e) {
        log.info("空指针异常,请求地址:{},错误信息:{}", request.getRequestURI(), e.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("message", e.getMessage());
        return map;
    }

}

我们再次请求接口,这次返回了规定格式的错误信息。

SpringBoot的几个高级技能的学习_SpringBoot_06