在实际开发中,可能需要打印方法的入参和返回的数据以帮助出现问题时可快递定位.
常规的做法在方法中的业务处理之前使用 Logger 打印方法入参,在业务处理之后打印结果数据,这样就会在很多方法中存在重复代码。
像打印日志这类跨多个业务和模块的需求,可以通过 Spring AOP 来统一实现,完全省略了方法中手动添加 Logger 的操作。
统一打印日志
统一打印日志可以集成 ELK
,归集这些日志数据,可以做一些统计分析工作,用来对功能和性能进行优化调整。
下面示例,使用前置和后置通知实现将 Controller 方法的入参和响应记录到日志,以方便快速定位问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| import com.alibaba.fastjson.JSON; import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.NamedThreadLocal; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream;
@Aspect @Component public class ReqRespLogAop {
private static final Logger logger = LogManager.getLogger(ReqRespLogAop.class);
private NamedThreadLocal<Long> threadLocal = new NamedThreadLocal("StopWatch");
@Pointcut("execution(* com.xxxxxx.controller.*.*(..))") public void pointcut() { }
@Before("pointcut()") public void logRequestParams(JoinPoint joinPoint) { threadLocal.set(System.currentTimeMillis());
Object[] args = joinPoint.getArgs(); Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args); List<Object> logArgs = stream.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile))).collect(Collectors.toList());
String fullClassName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String remoteHost = request.getRemoteHost(); logger.info("Request Client Host:{}", remoteHost); logger.info("Request URI:{}, API:{}, Request Body:{}", request.getRequestURI(), fullClassName + "." + methodName, JSON.toJSONString(logArgs)); }
@AfterReturning(returning = "response", pointcut = "pointcut()") public void logResponseBody(Object response) { if (response != null) { logger.info("Response Body:{}", JSON.toJSONString(response)); } Long timeConsume = (System.currentTimeMillis() - threadLocal.get()); if (timeConsume > (2 * 1000)) { logger.warn("请求处理耗时:{}", timeConsume + " ms"); } else { logger.info("请求处理耗时:{}", timeConsume + " ms"); } threadLocal.remove(); } }
|
如果还需要记录其它方法的入参和返回数据,例如记录业务层的方法处理
在切面类里可以定义多个切点,使用切入点表达式组合的方式来实现,参考 Spring Boot 2实践系列(四十八):Spring AOP详解与应用:切入点表达式组合。
解密及修改入参值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.clearofchina.core.exception.BusinessException; import com.clearofchina.prediagnose.annotate.UuidValid; import com.clearofchina.prediagnose.constants.SysConstants; import com.clearofchina.prediagnose.utils.CryptoUtil; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.NamedThreadLocal; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream;
@Aspect @Component public class RequestResponseLogAop {
private static final Logger logger = LogManager.getLogger(RequestResponseLogAop.class);
private NamedThreadLocal<Long> threadLocal = new NamedThreadLocal("StopWatch");
@Pointcut("execution(* com.*.controller.*.*(..))") public void pointcut() { }
@Before("pointcut()") public void logRequestInfo(JoinPoint joinPoint) { threadLocal.set(System.currentTimeMillis()); Object[] args = joinPoint.getArgs();
Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.stream(args); List<Object> logArgs = stream.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile))).collect(Collectors.toList());
String fullClassName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String remoteHost = request.getRemoteHost(); logger.info("Request Client Host:{}", remoteHost); logger.info("Request URI:{}, API:{}", request.getRequestURI(), fullClassName + "." + methodName); logger.info("Request Body:{}", JSON.toJSONString(logArgs)); this.validUuid(joinPoint, args); }
private void validUuid(JoinPoint joinPoint, Object[] args) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method targetMethod = methodSignature.getMethod(); UuidValid annotation = targetMethod.getAnnotation(UuidValid.class); if (ObjectUtils.isNotEmpty(annotation)) { Object obj = args[0]; JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(obj)); String uuid = (String) jsonObject.get("uuid"); String registerNo = null; try { registerNo = CryptoUtil.decryptDES(SysConstants.UUID_SECRET, uuid); } catch (Exception e) { e.printStackTrace(); throw new BusinessException("UUID错误"); }
try { Field field = obj.getClass().getDeclaredField("registerNo"); if (ObjectUtils.isNotEmpty(field)) { field.setAccessible(true); field.set(obj, registerNo); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
@AfterReturning(returning = "response", pointcut = "pointcut()") public void logResponseInfo(Object response) {
if (response instanceof ResponseEntity) { logger.info("Response Body:{}", "输出文件"); } else { logger.info("Response Body:{}", JSON.toJSONString(response)); }
Long timeConsume = (System.currentTimeMillis() - threadLocal.get()); if (timeConsume > (2 * 1000)) { logger.warn("请求处理耗时:{}", timeConsume + " ms"); } else { logger.info("请求处理耗时:{}", timeConsume + " ms"); } threadLocal.remove(); } }
|