思路
整体思路:
通过切面完成日志规整,提供针对类和方法的注解,通过需要打日志的主要方法加上注解,Controller和mq消费处这样的入口加上类注解,环绕通知来完成入参,执行时间的计算。后置通知和后置异常通知来完成threadLocal变量的释放,执行时间的打印和出参打印(threadLocal变量的释放只针对入口处,执行时间的打印和出参打印是针对所有加注解的地方)。
日志id传递问题:
为了将日志ID通过整条链路传下去,又不想每个方法都去写,切面又无法将Controller层的参数传到底层,因此选择的最终方案是:
logId在入口生成,并放到当前线程的threadlocal中。(注意:放到threadlocal的变量必须最终释放掉,否则变量一直不释放会导致内存溢出)
流程图
具体demo:
简单的自定义注解类:
针对整个类:一般用于controller层,整条链路的入口肯定都要日志记录
@Target({ElementType.TYPE})//注解用在类上
@Retention(RetentionPolicy.RUNTIME)//注解在运行时保留
@Documented//指定javadoc生成API文档时显示该注解信息
public @interface InvokeLog {
}
针对方法:一般用于其他非入口的类重点方法上,以便于问题排查
@Target({ElementType.METHOD})//注解用在方法上
@Retention(RetentionPolicy.RUNTIME)//注解在运行时保留
@Documented//指定javadoc生成API文档时显示该注解信息
public @interface InvokeLogMethod {
}
切面
切点函数:
因为每个切点函数只能指定一种切点表达式(写多个也只起作用一个),又需要针对controller和consume两特定路径下进行资源释放,因此只能写多个切点函数。
@Pointcut("execution(public * com.sgcc.controller.*.*(..))")//释放
public void pointCutForRemove() {
}
@Pointcut("execution(public * com.sgcc.mq.consum.MessageActivityListener.consume(..))")//释放
public void pointCutForRemove2() {
}
@Pointcut("@annotation(com.sgcc.utils.annotation.InvokeLogMethod)")//针对方法
public void pointCut() {
}
@Pointcut("@within(com.sgcc.utils.annotation.InvokeLog)")//针对类
public void pointCutForClass() {
}
环绕通知:
因为每个通知只能针对一种切点函数,因此也需要同样的通知写多个来达到通知对多个切点函数起作用的效果。
// 打印开始、请求报文、执行耗时
@Around("pointCutForClass()")
public Object logInvokeTime(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();//开始时间
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object result = new Object();
try {
printArgs(joinPoint);
//如果没有参数为空,执行方法
result = joinPoint.proceed();
}catch(Throwable e){
e.printStackTrace();
log.error(ERRPRINTARGS,logId,className,methodName,e.getMessage());
} finally {
long end = System.currentTimeMillis();//结束时间
executeTime = end - start;
}
return result;
}
// 打印开始、请求报文、执行耗时
@Around("pointCut()")
public Object logInvokeTimeForMethod(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();//开始时间
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object result = new Object();
try {
printArgs(joinPoint);
//如果没有参数为空,执行方法
result = joinPoint.proceed();
}catch(Throwable e){
e.printStackTrace();
log.error(ERRPRINTARGS,logId,className,methodName,e.getMessage());
} finally {
//计算出耗时,再有后置通知完成耗时的输出
long end = System.currentTimeMillis();
executeTime = end - start;
}
return result;
}
打印参数的方法代码:
private void printArgs(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
//目标切点的类名和方法名
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
try {
Object[] args = joinPoint.getArgs();
int argLength = args.length;
if (argLength == 0) return;
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < argLength; i++) {
Object arg = args[i];
if (IcCommonUtils.isNullForObject(arg)) continue;
if (arg instanceof Number || arg instanceof CharSequence || arg instanceof Character) {
buffer.append(arg);
} else {
buffer.append(JSON.toJSONString(arg));
}
if (i < argLength - 1) {
buffer.append(",");
}
}
//将参数流转换成字符串
String data = buffer.toString();
//如果获取不到,则代表是整条链路的入口,需新生成日志ID
if (IcCommonUtils.isNullForObject(threadLocal.get())){
logId = IcCommonUtils.createUUID();
threadLocal.set(logId);
}else {
logId= (String)threadLocal.get();
}
log.info(ARGS,InvokeLogAdvice.logId,className,methodName,data);
} catch (Exception e) {
log.error(ERRPRINTARGS,logId,className,methodName,e.getMessage());
}
}
后置返回通知和后置异常通知:
//后置通知 打印返回报文 调用结束
@AfterReturning(pointcut = "pointCut()", returning = "returnValue")
public void doAfterReturning(JoinPoint joinPoint, Object returnValue) {
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
log.info(RETMSG,logId,className,methodName,returnValue==null?"":returnValue.toString());
log.info(END,logId,className,methodName,executeTime);
}
//例外通知 异常 调用结束
@AfterThrowing(pointcut = "pointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) throws Exception {
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
log.error(EX,logId,className,methodName,e.getMessage());
log.info(END,logId,className,methodName,executeTime);
}
//后置通知 打印返回报文 调用结束
@AfterReturning(pointcut = "pointCutForClass()", returning = "returnValue")
public void doAfterReturningForClass(JoinPoint joinPoint, Object returnValue) {
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
log.info(RETMSG,logId,className,methodName,returnValue==null?"":returnValue.toString());
log.info(END,logId,className,methodName,executeTime);
}
//例外通知 异常 调用结束
@AfterThrowing(pointcut = "pointCutForClass()", throwing = "e")
public void doAfterThrowingForClass(JoinPoint joinPoint, Exception e) throws Exception {
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
log.error(EX,logId,className,methodName,e.getMessage());
log.info(END,logId,className,methodName,executeTime);
}
//后置通知 打印返回报文 调用结束 -针对controller层进行清除threadLocal,防止内存泄漏com.sgcc.mq.consum.MessageActivityListener
@AfterReturning(pointcut = "pointCutForRemove()", returning = "returnValue")
public void doAfterReturningController(JoinPoint joinPoint, Object returnValue) {
threadLocal.remove();
}
//例外通知 异常 调用结束 -针对controller层进行清除threadLocal,防止内存泄漏
@AfterThrowing(pointcut = "pointCutForRemove()", throwing = "e")
public void doAfterThrowingController(JoinPoint joinPoint, Exception e) throws Exception {
threadLocal.remove();
}
//后置通知 打印返回报文 调用结束 -针对controller层进行清除threadLocal,防止内存泄漏com.sgcc.mq.consum.MessageActivityListener
@AfterReturning(pointcut = "pointCutForRemove2()", returning = "returnValue")
public void doAfterReturningMq(JoinPoint joinPoint, Object returnValue) {
threadLocal.remove();
}
//例外通知 异常 调用结束 -针对controller层进行清除threadLocal,防止内存泄漏
@AfterThrowing(pointcut = "pointCutForRemove2()", throwing = "e")
public void doAfterThrowingMq(JoinPoint joinPoint, Exception e) throws Exception {
threadLocal.remove();
}