SpringBoot使用AOP统一处理Web请求日志添加MDC

转自:https://blog.csdn.net/qq_34021712/article/details/78680915

AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

之前看到公司有的人,在业务逻辑内打印方法的请求参数,添加MDC,请求返回时再打印返回的结果,这种事可以统一写好,所有地方都用再写了,并且也能保证代码的干净整洁。

SpringAOP.java

[java]  view plain  copy
  1. package com.ruubypay.miss.usercenter.config;  
  2.   
  3. import com.ruubypay.miss.global.utils.ErrorCodeHelper;  
  4. import com.ruubypay.miss.usercenter.interfaces.models.base.ModelsReturn;  
  5. import javassist.*;  
  6. import javassist.bytecode.CodeAttribute;  
  7. import javassist.bytecode.LocalVariableAttribute;  
  8. import javassist.bytecode.MethodInfo;  
  9. import org.apache.commons.lang3.ArrayUtils;  
  10. import org.aspectj.lang.JoinPoint;  
  11. import org.aspectj.lang.ProceedingJoinPoint;  
  12. import org.aspectj.lang.annotation.*;  
  13. import org.aspectj.lang.reflect.MethodSignature;  
  14. import org.slf4j.Logger;  
  15. import org.slf4j.LoggerFactory;  
  16. import org.slf4j.MDC;  
  17. import org.springframework.context.annotation.Configuration;  
  18. import org.springframework.core.annotation.Order;  
  19. import org.springframework.stereotype.Component;  
  20. import org.springframework.web.context.request.RequestContextHolder;  
  21. import org.springframework.web.context.request.ServletRequestAttributes;  
  22.   
  23. import javax.servlet.http.HttpServletRequest;  
  24. import javax.servlet.http.HttpServletResponse;  
  25. import java.util.Arrays;  
  26. import java.util.Enumeration;  
  27. import java.util.UUID;  
  28.   
  29. /** 
  30.  * @author: wangsaichao 
  31.  * @date: 2017/10/25 
  32.  * @description: 配置切面,配置日志 和请求参数打印 
  33.  */  
  34. @Aspect  
  35. //@Order(-99) // 控制多个Aspect的执行顺序,越小越先执行,为了要在Spring的事务之后执行,所以给他设置99  
  36. @Configuration  
  37. public class SpringAOP {  
  38.   
  39.     private static final Logger logger = LoggerFactory.getLogger(SpringAOP.class);  
  40.   
  41.     /** 
  42.      * 定义切点Pointcut 
  43.      * 第一个*号:表示返回类型, *号表示所有的类型 
  44.      * 第二个*号:表示类名,*号表示所有的类 
  45.      * 第三个*号:表示方法名,*号表示所有的方法 
  46.      * 后面括弧里面表示方法的参数,两个句点表示任何参数 
  47.      */  
  48.     @Pointcut("execution(* com.ruubypay.miss.usercenter.interfaces.controller..*.*(..))")  
  49.     public void executionService() {  
  50.   
  51.     }  
  52.   
  53.   
  54.     /** 
  55.      * 方法调用之前调用 
  56.      * @param joinPoint 
  57.      */  
  58.     @Before(value = "executionService()")  
  59.     public void doBefore(JoinPoint joinPoint){  
  60.   
  61.         //添加日志打印  
  62.         String requestId = String.valueOf(UUID.randomUUID());  
  63.         MDC.put("requestId",requestId);  
  64.         logger.info("=====>@Before:请求参数为:{}",Arrays.toString(joinPoint.getArgs()));  
  65.   
  66.     }  
  67.   
  68.     /** 
  69.      * 方法之后调用 
  70.      * @param joinPoint 
  71.      * @param returnValue 方法返回值 
  72.      */  
  73.     @AfterReturning(pointcut = "executionService()",returning="returnValue")  
  74.     public void  doAfterReturning(JoinPoint joinPoint,Object returnValue){  
  75.   
  76.         logger.info("=====>@AfterReturning:响应参数为:{}",returnValue);  
  77.         // 处理完请求,返回内容  
  78.         MDC.clear();  
  79.     }  
  80.   
  81.     /** 
  82.      * 统计方法执行耗时Around环绕通知 
  83.      * @param joinPoint 
  84.      * @return 
  85.      */  
  86.     @Around("executionService()")  
  87.     public Object timeAround(ProceedingJoinPoint joinPoint) {  
  88.   
  89.         //获取开始执行的时间  
  90.         long startTime = System.currentTimeMillis();  
  91.   
  92.         // 定义返回对象、得到方法需要的参数  
  93.         Object obj = null;  
  94.         //Object[] args = joinPoint.getArgs();  
  95.   
  96.         try {  
  97.             obj = joinPoint.proceed();  
  98.         } catch (Throwable e) {  
  99.             logger.error("=====>统计某方法执行耗时环绕通知出错", e);  
  100.         }  
  101.   
  102.         // 获取执行结束的时间  
  103.         long endTime = System.currentTimeMillis();  
  104.         //MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
  105.         //String methodName = signature.getDeclaringTypeName() + "." + signature.getName();  
  106.         // 打印耗时的信息  
  107.         logger.info("=====>处理本次请求共耗时:{} ms",endTime-startTime);  
  108.         return obj;  
  109.     }  
  110.   
  111.   
  112. }  
一些解释,在代码中已经解释的很清楚了,这里介绍一下MDC.put("requestId",requestId);

假如你的系统已经上线,有一个客户的操作有误,导致出现了一些错误,我们可以到日志中去看一下用户的这次请求到底是发生了什么错误。我们可以根据根据用户的手机号或者账号来定位到日志的位置,但是下面的处理流程呢。我们知道系统不可能只有一个人在访问,假如很多人在访问的话,日志打印的是很乱的,这时候如果没有MDC我猜此时此刻你应该处于雪崩状态。MDC恰到好处的让你能够实现在日志上突如其来的一些需求。

requestId我使用的uuid来作为value值.方便 唯一。在我们根据手机号定位到用户的请求时,在使用requestId来定位这次的请求流程,具体如下:

可以看到,可以直接定位出这次请求的所有日志,就算并发很大,只要保证requestId的唯一,就可以定位这次请求的处理过程。

MDC.put("requestId",requestId);之后 还要在日志中 使用requestId才可以,如下:

===%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L [%X{requestId}] - %msg%n

最后使用完之后,使用 MDC.clear();来清除这次请求的requestId.


猜你喜欢

转载自blog.csdn.net/weixin_42244363/article/details/80376087