前言
在 SpringBoot 中使用自定义注解, aop 切面打印 web 请求日志. 主要是想把 controller 的每个 request 请求日志收集起来, 调用接口, 执行时间, 返回值这几个重要的信息存储到数据库里, 然后可以使用火焰图统计接口调用时长, 平均响应时长, 以便于我们对接口的调用和执行情况及时掌握.
添加依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- <version>2.1.4.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-Web</artifactId>
- <version>2.1.4.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- <version>2.1.4.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>2.8.5</version>
- </dependency>
自定义注解
- import java.lang.annotation.*;
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- @Documented
- public @interface WebLogger {
- String value() default "";
- }
AOP 定义切面, 切点
在实现切面之前先要了解几个 aop 的注解:
@Aspect,
@Pointcut 定义切点, 后面跟随一个表达式, 表达式可以是一个注解, 也可以具体到方法级别.
- @Before
- @After
- @Around
实现切面, 在 before 方法里统计 request 请求相关参数, 在 around 方法里计算接口调用时间. 这里统计请求的参数时有个 bug,joinpoint.getArgs 返回值是一个 Object 数组, 但是数组里只有参数值, 没有参数名. 还没找到解决办法.
- @Aspect
- @Component
- public class WebLoggerAspect {
- @Pointcut("@annotation(com.zhangfei.anno.WebLogger)"
- public void log() {}
- @Around("log()")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- long startTime=System.currentTimeMillis();
- Object result=joinPoint.proceed();
- System.out.println("Response:"+new Gson().toJson(result));
- System.out.println("耗时:"+(System.currentTimeMillis()-startTime));
- return result;
- }
- @Before("log()")
- public void doBefore(JoinPoint joinPoint) {
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
- System.out.println("==================Start=================");
- System.out.println("URL:" + request.getRequestURL().toString());
- System.out.println("Description:" + getLogValue(joinPoint));
- System.out.println("Method:" + request.getMethod().toString());
- // 打印 controller 全路径及 method
- System.out.println("Class Method:" + joinPoint.getSignature().getDeclaringTypeName() + "," + joinPoint.getSignature().getName());
- System.out.println("客户端 IP:" + request.getRemoteAddr());
- System.out.println("请求参数:" + new Gson().toJson(joinPoint.getArgs()));
- }
- private String getLogValue(JoinPoint joinPoint) {
- MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
- Method method = methodSignature.getMethod();
- WebLogger webLogger = method.getAnnotation(WebLogger.class);
- return webLogger.value();
- }
- @After("log()")
- public void doAfter() {
- System.out.println("==================End=================");
- }
- }
怎么使用注解?
这里就直接在你的 Web 请求方法上直接添加 WebLogger 注解
- @Controller
- @RequestMapping("/student")
- public class StudentController {
- private final Logger logger= LoggerFactory.getLogger(StudentController.class);
- @GetMapping("/list")
- @WebLogger("学生列表")
- public @ResponseBody List<Student> list(){
- ArrayList<Student> list=new ArrayList<>();
- Student student0=new Student(1,"kobe",30);
- Student student1=new Student(2,"james",30);
- Student student2=new Student(3,"rose",30);
- list.add(student0);
- list.add(student1);
- list.add(student2);
- return list;
- }
- @WebLogger("学生实体")
- @RequestMapping("/detail")
- public @ResponseBody Student student(int id){
- return new Student(1,"kobe",30);
- }
- }
切面日志输出效果
总结
从头条上一位朋友文章评论上看到有人提出, 在分布式部署的环境中, 出现高并发时日志打印会出现错乱的情况, 这里还需要把线程 id 或者声明一个 guid, 存入 ThreadLoal 中统计使用. 当然更多的人简历还是使用 ELK 等分布式日志解决方法, 好吧, 这些还有待于自己部署环境去尝试.
来源: https://www.cnblogs.com/sword-successful/p/10850168.html