一、统计接口耗时
通过日志记录接口耗时情况对于性能监控、故障排查等方面具有非常重要意义。而实现这一功能的方式多种多样,包括使用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;
}
}
启动程序,调用接口访问
我们查看控制台上打印出了请求接口相关的耗时信息。
二、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;
}
}
请求该接口,日志输出
针对该接口确实输出的信息比较多了,有请求的有响应,但是发现关键信息都被掩码了,这对我们调试接口没有任何的帮助。通过如下配置,开启请求明细信息输出。
spring.mvc.log-request-details=true
开启该属性后,日志输出
关键的信息都输出了,这非常有助于接口调试,生产环境相信你绝对不可能这样输出日志的。
三、404问题
当我们访问一个不存在的接口时,默认情况下会输出如下错误信息(这会根据你当前请求header中的accept来决定输出格式)
浏览器访问不存在的接口。
我们需要在配置文件添加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;
}
}
我们再次请求接口,这次返回了规定格式的错误信息。