目标
由于项目是使用的是几年前的开源项目,只是实现了基础的CRUD业务功能,缺少基础设施建设,请求一个接口也没有任何日志打印,即使异常也不会有任何日志抛出,导致排查问题很困难,于是对其进行了二次改造。
开启SQL打印
在logback.xml中修改打印日志的级别
结果:每次请求打印了SQL,但是参数信息并没有打印,联调有问题时需要向前端要参数模拟出错情况的参数排查,于是采用了下面的方式来打印参数
接口请求处硬编码打印请求参数
在接口请求处,采用硬编码,标识是请求哪一个接口,传了什么参数,但是发现并不打印异常信息,如果想打印,必须在每个方法加上 try-catch捕获打印才行,于是使用切面统一拦截打印入参出参、接口耗时和异常信息等等
使用AOP切面统一处理
统一拦截处理控制层的所有方法,于是针对带有 @RestController 的所有类,都进行切面处理。
首先定义一个切面类 LogAspect ,并加上注解 @Aspect 声明这是一个切面类,并加上 @Comonent,表明将该类交由Spring管理
然后声明切点,指定切入范围,使用 @Pointcut,指定范围,因为是全局统一日志拦截,所以切入的范围是所有加 @RestController 注解的类。
注解表达式
标准的pointcut的表达式是很丰富的,但spring aop只支持其中的9种,外加Spring aop自己扩充的一种一共是10种类型的表达式,分别如下
- execution:一般用于指定方法执行,用的最多
- within:指定某些类型的全部方法执行,也可用来指定一个包
- this:Spring aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,spring aop将生效
- target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,spring aop将生效
- args:当执行的方法的参数是指定类型时生效
- @target:当代理的目标对象拥有指定的注解时生效
- @args:当执行的方法参数类型上拥有指定的注解时生效
- @within:与@target类型,官方文档描述@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解
- @annotation:当执行的方法上拥有指定注解时生效
- bean:当调用的方法是指定的bean的方法时生效
最后指定切点,执行目标方法,执行之后打印入参、接口名、接口耗时等信息,并且将目标方法try-catch住,如果目标方法异常,则打印错误日志提示相关错误信息,以便排查错误的原因。
- @Around:增强处理
- @Before:表示在切点方法之前执行
- @After:表示在切点方法之后执行
- @AfterRetruning:与@After相似
- @AfterThrowing:当切点方法抛出异常时会执行
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Component
@Slf4j
@Aspect
public class LogAspect {
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
Object result;
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
assert sra != null;
HttpServletRequest request = sra.getRequest();
String requestId = MDC.get("X-Request-Id");
long startMills = System.currentTimeMillis();
long cost = 0;
try {
result = pjp.proceed();
cost = System.currentTimeMillis() - startMills;
log.info("请求结束!本次请求耗时:{}, rid : {} ,url: {}, method: {}, params: {},user:{}, Authorization: {}, 响应结果:{}",
cost, requestId, request.getRequestURL().toString(),
request.getMethod(), pjp.getArgs(), JSON.toJSONString(request.getAttribute("user")), request.getHeader("Authorization"),
JSON.toJSONString(result, SerializerFeature.DisableCircularReferenceDetect));
} catch (Exception e) {
log.error("请求异常!!!本次请求耗时:{}, rid : {}, error:{} ,url: {}, method: {}, params: {},user:{}, Authorization: {}", cost,requestId, e, request.getRequestURL().toString(),
request.getMethod(), pjp.getArgs(), JSON.toJSONString(request.getAttribute("user")), request.getHeader("Authorization"));
throw e;
}
return result;
}
}