日常甩锅系列之Aop日志
项目开发到联调阶段的时候,会经常出现和前端进行扯皮的操作,你说你有理,他说他有理,一般的操作就是:
前端:我肯定传递了,你看下我的参数
后台:我写的接口不可能出问题,100%可靠,你在请求一下,我看下服务器日志,刚才日志过多,你在请求一次
陷入了无线循环,但是今天我们有了日志切面就可以规避这些问题了,直接就能定位基础的请求入参及响应内容,方便后台排查问题及及时帅锅,开玩笑的,下面进入正题。
简单了解一下AOP
本片文章只会大致说明一下aop的基本概念及使用,详细内容请参考官方文档或网上各路大神的文章。
@Aspect:声明该类为一个注解类
@Slf4j //lombok 日志支持
@Component //注入到spring环境
@Aspect //定义切面
public class LogAspect {......}
maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解
//定义一个切点
@Pointcut("execution(* com.springboot.example.controller..*(..))")
public void logPointcut() {......}
切点定义好后,就是围绕这个切点做文章了:
-
@Before: 前置增强,执行方法之前,编入相关代码,本片文章我们使用**@Around**
@Before("logPointcut()")//上面定义的切点 public void logBefore(JoinPoint joinPoint) {......} -
@After: 后置增强,执行方法之后,编入相关代码,本片文章我们使用**@Around**
@Before("logPointcut()")//上面定义的切点 public void logAfter(JoinPoint joinPoint) {......} -
@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景
-
@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")//上面定义的切点,异常后进入 public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) -
@Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点
@Around("logPointcut()")//上面定义的切点 public Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {}
完整代码
@Slf4j
@Component
@Aspect
public class LogAspect {
//是否格式化输出json,便于排查问题
@Value("${request.info.log.beautiful.json:1")
private String beautifulJson;
@Pointcut("execution(* com.springboot.example.controller..*(..))")
public void logPointcut() {}
@Around("logPointcut()")
public Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long start = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Object result = proceedingJoinPoint.proceed();
RequestInfo requestInfo = new RequestInfo();
requestInfo.setIp(request.getRemoteAddr());
requestInfo.setUrl(request.getRequestURL().toString());
requestInfo.setHttpMethod(request.getMethod());
requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
proceedingJoinPoint.getSignature().getName()));
requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
requestInfo.setResult(result);
requestInfo.setTimeCost(System.currentTimeMillis() - start);
if (Objects.equals(beautifulJson, "0")) {
log.info("Request Info : {}", JSONUtil.toJsonStr(requestInfo));
} else {
log.info("Request Info : {}", JSONUtil.toJsonPrettyStr(requestInfo));
}
return result;
}
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")
public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
requestErrorInfo.setIp(request.getRemoteAddr());
requestErrorInfo.setUrl(request.getRequestURL().toString());
requestErrorInfo.setHttpMethod(request.getMethod());
requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName()));
requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
requestErrorInfo.setException(ExceptionUtil.getMessage(e));
log.info("Error Request Info : {}", JSONUtil.toJsonStr(requestErrorInfo));
}
/**
* 获取入参
*
* @param proceedingJoinPoint
* @return
*/
private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
//参数名
String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
//参数值
Object[] paramValues = proceedingJoinPoint.getArgs();
return buildRequestParam(paramNames, paramValues);
}
private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
//参数名
String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
//参数值
Object[] paramValues = joinPoint.getArgs();
return buildRequestParam(paramNames, paramValues);
}
private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
Map<String, Object> requestParams = new HashMap<>();
try {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
Map<String, String[]> urlRequestParams = request.getParameterMap();
if (CollectionUtil.isNotEmpty(urlRequestParams)) {
requestParams.put("urlParams", urlRequestParams);
}
} catch (Exception exception) {
log.error("获取url参数异常 - {}", exception.getMessage(), exception);
}
for (int i = 0; i < paramNames.length; i++) {
Object value = paramValues[i];
//如果是文件对象
if (value instanceof MultipartFile) {
MultipartFile file = (MultipartFile) value;
//获取文件名
value = file.getOriginalFilename();
}
if (!(value instanceof HttpServletRequest) && !(value instanceof HttpServletResponse)) {
requestParams.put(paramNames[i], value);
}
}
return requestParams;
}
@Data
public class RequestInfo {
private String ip;
private String url;
private String httpMethod;
private String classMethod;
private Object requestParams;
private Object result;
private Long timeCost;
}
@Data
public class RequestErrorInfo {
private String ip;
private String url;
private String httpMethod;
private String classMethod;
private Object requestParams;
private String exception;
}
}
测试
测试代码
@RestController
public class TestController {
/**
* 测试get参数
*
* @param name 测试参数
* @return 结果
*/
@GetMapping(value = "/test/get")
public Object testGet(@RequestParam(value = "name", required = false) String name) {
return R.ok(StrUtil.isNotBlank(name) ? name : "Hello world!");
}
/**
* 测试post参数
*
* @param user 测试参数
* @return 结果
*/
@PostMapping(value = "/test/post")
public Object testPost(@RequestBody User user) {
return R.ok(user);
}
}
访问测试
crul http://localhost:8080/test/get
crul http://localhost:8080/test/post
测试结果
这样是不是很方便帅锅了,当然最重要的是可以很方便的排查是否为参数异常问题,上面只是测试了get参数,post参数需要你们自行测试了。
总结
以上就是本次文章的全部内容了,本片文章只是简单的介绍了aop打印请求日志的基本使用,还可以入库记录重要的三方日志,请大家自行研究,本文章只是给大家参考而已!