Skip to content

@SysLog 记录操作日志

  1. 配置文件介绍
yaml
acuity:
  log:
    enabled: false  # false=禁止记录操作日志 true=开启
    type: DB   # 日志存储位置   DB=数据库 (base_operation_log)  LOGGER=日志文件
acuity:
  log:
    enabled: false  # false=禁止记录操作日志 true=开启
    type: DB   # 日志存储位置   DB=数据库 (base_operation_log)  LOGGER=日志文件
  1. 注解属性介绍详看@WebLog中的注释! 其中value属性是支持SpEL表达式的.
  2. 注解使用举例
java
// 禁用操作日志
@WebLog(enabled = false)
//  response = false不记录方法的响应参数
@WebLog(value = "'分页列表查询:第' + #params?.current + '页, 显示' + #params?.size + '行'", response = false)
// requestByError = false 表示, 方法报错时, 也不记录请求参数
@WebLog(value = "'保存订单:订单编码' + #data?.code + ', 订单Id:' + #data?.name", request = false, requestByError = false)
// 禁用操作日志
@WebLog(enabled = false)
//  response = false不记录方法的响应参数
@WebLog(value = "'分页列表查询:第' + #params?.current + '页, 显示' + #params?.size + '行'", response = false)
// requestByError = false 表示, 方法报错时, 也不记录请求参数
@WebLog(value = "'保存订单:订单编码' + #data?.code + ', 订单Id:' + #data?.name", request = false, requestByError = false)
  1. WebLog 注解参数详解
java
public @interface WebLog {
    /**
     * 是否启用 操作日志
     * 禁用控制优先级:acuity.log.enabled = false > 控制器类上@SysLog(enabled = false) > 控制器方法上@SysLog(enabled = false)
     *
     * @return 是否启用
     */
    boolean enabled() default true;

    /**
     * 操作日志的描述, 支持spring 的 SpEL 表达式。
     *
     * @return {String}
     */
    String value() default "";

    /** 模块 */
    String modular() default "";

    /**
     * 是否拼接Controller类上@Api注解的描述值
     *
     * @return 是否拼接Controller类上的描述值
     */
    boolean controllerApiValue() default true;

    /**
     * 是否记录方法的入参
     *
     * @return 是否记录方法的入参
     */
    boolean request() default true;

    /**
     * 若设置了 request = false、requestByError = true,则方法报错时,依然记录请求的入参
     *
     * @return 当 request = false时, 方法报错记录请求参数
     */
    boolean requestByError() default true;

    /**
     * 是否记录返回值
     *
     * @return 是否记录返回值
     */
    boolean response() default true;
}
public @interface WebLog {
    /**
     * 是否启用 操作日志
     * 禁用控制优先级:acuity.log.enabled = false > 控制器类上@SysLog(enabled = false) > 控制器方法上@SysLog(enabled = false)
     *
     * @return 是否启用
     */
    boolean enabled() default true;

    /**
     * 操作日志的描述, 支持spring 的 SpEL 表达式。
     *
     * @return {String}
     */
    String value() default "";

    /** 模块 */
    String modular() default "";

    /**
     * 是否拼接Controller类上@Api注解的描述值
     *
     * @return 是否拼接Controller类上的描述值
     */
    boolean controllerApiValue() default true;

    /**
     * 是否记录方法的入参
     *
     * @return 是否记录方法的入参
     */
    boolean request() default true;

    /**
     * 若设置了 request = false、requestByError = true,则方法报错时,依然记录请求的入参
     *
     * @return 当 request = false时, 方法报错记录请求参数
     */
    boolean requestByError() default true;

    /**
     * 是否记录返回值
     *
     * @return 是否记录返回值
     */
    boolean response() default true;
}

原理:

说明

通过AOP拦截标记了@WebLog 的方法, 拦截后, SysLogAspect 中的方法用于获取被拦截方法的入参和返回值. 然后发布一个 SysLogEvent 事件 (为了让操作日志尽可能少的影响方法调用时长,采用事件方式来异步处理, 大家也可以改成消息队列之类的), SysLogListener 监听器接收到事件后, 调用consumer.accept 方法, 让对操作日志进行存储.

使用步骤:

  1. 在 controller上标记 @WebLog

  2. 启动项目,并调用controller方法

  3. AOP切面拦截请求,并记录入参和出参

    java
    @Aspect
    public class SysLogAspect {
        @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
        public void doAfterReturning(JoinPoint joinPoint, Object ret) {
          
        }
      
        @Before(value = "sysLogAspect()")
        public void doBefore(JoinPoint joinPoint) {
          
        }
    }
    @Aspect
    public class SysLogAspect {
        @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
        public void doAfterReturning(JoinPoint joinPoint, Object ret) {
          
        }
      
        @Before(value = "sysLogAspect()")
        public void doBefore(JoinPoint joinPoint) {
          
        }
    }
  4. 记录好参数后,发布事件待消费者处理参数

    java
    private void publishEvent(OptLogDTO sysLog) {
      sysLog.setFinishTime(LocalDateTime.now());
      sysLog.setConsumingTime(sysLog.getStartTime().until(sysLog.getFinishTime(), ChronoUnit.MILLIS));
      SpringUtils.publishEvent(new SysLogEvent(sysLog));
      THREAD_LOCAL.remove();
    }
    private void publishEvent(OptLogDTO sysLog) {
      sysLog.setFinishTime(LocalDateTime.now());
      sysLog.setConsumingTime(sysLog.getStartTime().until(sysLog.getFinishTime(), ChronoUnit.MILLIS));
      SpringUtils.publishEvent(new SysLogEvent(sysLog));
      THREAD_LOCAL.remove();
    }
  5. 消费者监听到事件,并将日志存储

    java
    public class SysLogListener {
    
        private final Consumer<OptLogDTO> consumer;
    
        @Async
        @Order
        @EventListener(SysLogEvent.class)
        public void saveSysLog(SysLogEvent event) {
            OptLogDTO sysLog = (OptLogDTO) event.getSource();
    
            // 非租户模式 (NONE) , 需要修改这里的判断
            if (sysLog == null || sysLog.getTenantId() == null) {
                log.warn("租户编码不存在,忽略操作日志=={}", sysLog != null ? sysLog.getRequestUri() : "");
                return;
            }
            ContextUtil.setTenantId(sysLog.getTenantId());
    
          
          	// accept 方法是一个回调,具体的存储逻辑需要各个服务自己实现
            consumer.accept(sysLog);
        }
    }
    public class SysLogListener {
    
        private final Consumer<OptLogDTO> consumer;
    
        @Async
        @Order
        @EventListener(SysLogEvent.class)
        public void saveSysLog(SysLogEvent event) {
            OptLogDTO sysLog = (OptLogDTO) event.getSource();
    
            // 非租户模式 (NONE) , 需要修改这里的判断
            if (sysLog == null || sysLog.getTenantId() == null) {
                log.warn("租户编码不存在,忽略操作日志=={}", sysLog != null ? sysLog.getRequestUri() : "");
                return;
            }
            ContextUtil.setTenantId(sysLog.getTenantId());
    
          
          	// accept 方法是一个回调,具体的存储逻辑需要各个服务自己实现
            consumer.accept(sysLog);
        }
    }
  6. 业务服务,根据自己的需求配置日志存储方式

    java
    @Configuration
    public class BaseWebConfiguration extends BaseConfig {
    
        /**
         * acuity.log.enabled = true 并且 acuity.log.type=DB时实例该类
         */
        @Bean
        @ConditionalOnExpression("${acuity.log.enabled:true} && 'DB'.equals('${acuity.log.type:LOGGER}')")
        public SysLogListener sysLogListener(BaseOperationLogService logApi) {
          /* accept 方法是一个回调,具体的存储逻辑需要各个服务自己实现
           *
           * SysLogListener中执行 consumer.accept(sysLog); 后,就会执行:
           * data -> logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class))
           */
          return new SysLogListener(data -> logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class)));
        }
    }
    @Configuration
    public class BaseWebConfiguration extends BaseConfig {
    
        /**
         * acuity.log.enabled = true 并且 acuity.log.type=DB时实例该类
         */
        @Bean
        @ConditionalOnExpression("${acuity.log.enabled:true} && 'DB'.equals('${acuity.log.type:LOGGER}')")
        public SysLogListener sysLogListener(BaseOperationLogService logApi) {
          /* accept 方法是一个回调,具体的存储逻辑需要各个服务自己实现
           *
           * SysLogListener中执行 consumer.accept(sysLog); 后,就会执行:
           * data -> logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class))
           */
          return new SysLogListener(data -> logApi.save(BeanPlusUtil.toBean(data, BaseOperationLogSaveVO.class)));
        }
    }

欢迎使用天源云Saas快速开发系统