Spring AOP无侵入实现日志
平常我们聊起Spring Aop的使用场景最多的就是权限拦截,统一日志等。那么什么样的日志是比较优雅的实现方式?我们在开始实现日志前首先思考,什么样的日志是好的日志?需要满足哪些属性?达到什么样的目的?
简介
日志好比叙事,谁在什么时间发生了什么事,事情的结果是什么?满足这4点就能算得上合格的日志。
实现
- 定义一个日志描述类,描述日志产生的用户(系统)、主体、时间、地点、行为、结果、变化过程、异常等。
@Setter
@Getter
@Builder
@ToString
public class Log implements Serializable {
/**
* 日志唯一id,用于标记日志等唯一性
*/
private Serializable id;
/**
* 日志发生在那个系统
*/
private System system;
/**
* 日志发生在那个模块
*/
private String module;
/**
* 日志发生时间
*/
private LocalDateTime time;
/**
* 谁产生等日志
*/
private String user;
/**
* 本次日志等行为
*/
private String action;
/**
* 本次日志行为类型
*/
private ActionType actionType;
/**
* 行为结果
*/
private Status status;
/**
* 主体ID
* 订单ID,用户ID
*/
private Serializable subjectId;
/**
* 行为主体
* 订单,用户,收获地址一般可能是领域对象
*/
private String subject;
/**
* 本次行为的参数
*/
private String parmas;
/**
* 行为发生前的值
*/
private String preValue;
/**
* 行为发生后的值
*/
private String afterValue;
/**
* 本次行为发生异常的信息
*/
private String exception;
- 通过自定义注解,在我们需要的函数上打上注解完成日志记录的同时,对已有的代码达到无侵入;
@Component
@Aspect
@Slf4j
public class LogAspect {
@AfterReturning(pointcut = "@annotation(l0g)",returning = "result")
public void afterReturning(JoinPoint joinPoint, L0g l0g, Object result){
me.Cheng.t.domain.Log loog = me.Cheng.t.domain.Log.builder().actionType(l0g.actionType())
.action(l0g.action())
.id(UUID.randomUUID())
.system(System.ORDER)
.module("订单管理")
.user("zhangsan")
.time(LocalDateTime.now())
.status(Status.SUCCESS)
.build();
if(LogContextHandler.get().isPresent()){
LogContext logContext = LogContextHandler.get().get();
loog.setPreValue(logContext.getPreValue());
loog.setAfterValue(logContext.getAfterValue());
}
//这里可以进行本地记录
log.info("l000000g:{}",loog);
//这里可以上报日志系统
}
@AfterThrowing(pointcut = "@annotation(l0g)",throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, L0g l0g, Throwable exception){
Object[] args = joinPoint.getArgs();
String arg = (String)args[0];
me.Cheng.t.domain.Log loog = me.Cheng.t.domain.Log.builder().actionType(l0g.actionType())
.action(l0g.action())
.id(UUID.randomUUID())
.system(System.ORDER)
.module("订单管理")
.user("zhangsan")
.time(LocalDateTime.now())
.status(Status.EXCEPTION)
.parmas(arg)
.exception(exception.getMessage())
.build();
if(LogContextHandler.get().isPresent()){
LogContext logContext = LogContextHandler.get().get();
loog.setPreValue(logContext.getPreValue());
loog.setAfterValue(logContext.getAfterValue());
}
//这里可以进行本地记录
log.info("l000000g:{}",loog);
//这里可以上报日志系统
}
- 在服务层完成日志的收集
@Service
public class OrderServiceImpl implements OrderService {
@L0g(actionType = ActionType.MODIFY,action = "新增订单")
@Override
public void updateOrder(Order order) {
LogContextHandler.setLogContext(new LogContext().preValue("\"{price:5.00}\"").afterValue("\"{price:6.00}\""));
}
@L0g(actionType = ActionType.DELETE,action = "删除订单")
@Override
public void deleteOrder(String orderId) {
throw new RuntimeException("订单不存在");
}
}
- 通过本地线程达到日志收集过程中参数的传递
public class LogContextHandler {
private static final ThreadLocal<LogContext> threadLocal = new ThreadLocal<>();
public static void setLogContext(LogContext logContext){
threadLocal.set(logContext);
}
public static Optional<LogContext> get(){
return Optional.ofNullable(threadLocal.get());
}
}
所谓的无侵入只发生在记录日志行为,要想记录行为本身产生的数据,始终是需要少量的日志代码侵入。这样的代价是无法避免的也是值得的。
当然该Demo也放在了这里