4.2.3 日志持久化逻辑
同样在 LogRecordInterceptor 的代码中引用了 ILogRecordService,这个 Service 主要包含了日志记录的接口。
publicinterfaceILogRecordService {
/**
* 保存 log
*
* @param logRecord 日志实体
*/
voidrecord(LogRecord logRecord);
}
业务可以实现这个保存接口,然后把日志保存在任何存储介质上。这里给了一个 2.2 节介绍的通过 log.info 保存在日志文件中的例子,业务可以把保存设置成异步或者同步,可以和业务放在一个事务中保证操作日志和业务的一致性,也可以新开辟一个事务,保证日志的错误不影响业务的事务。业务可以保存在 Elasticsearch、数据库或者文件中,用户可以根据日志结构和日志的存储实现相应的查询逻辑。
@Slf4j
publicclassDefaultLogRecordServiceImplimplementsILogRecordService {
@Override
// @Transactional(propagation = Propagation.REQUIRES_NEW)
publicvoidrecord(LogRecord logRecord) {
log.info("【logRecord】log={}", logRecord);
}
}
4.2.4 Starter 逻辑封装
上面逻辑代码已经介绍完毕,那么接下来需要把这些组件组装起来,然后让用户去使用。在使用这个组件的时候只需要在 Springboot 的入口上添加一个注解 @EnableLogRecord(tenant = "com.mzt.test")。其中 tenant 代表租户,是为了多租户使用的。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableTransactionManagement
@EnableLogRecord(tenant= "com.mzt.test")
publicclassMain {
publicstaticvoidmain(String[] args) {
SpringApplication.run(Main.class, args);
}
}
我们再看下 EnableLogRecord 的代码,代码中 Import 了 LogRecordConfigureSelector.class
,在 LogRecordConfigureSelector
类中暴露了 LogRecordProxyAutoConfiguration
类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogRecordConfigureSelector.class)
public @interfaceEnableLogRecord{
String tenant();
AdviceMode mode()default AdviceMode.PROXY;
}
LogRecordProxyAutoConfiguration
就是装配上面组件的核心类了,代码如下:
@Configuration
@Slf4j
publicclassLogRecordProxyAutoConfigurationimplementsImportAware {
private AnnotationAttributes enableLogRecord;
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public LogRecordOperationSource logRecordOperationSource() {
returnnew LogRecordOperationSource();
}
@Bean
@ConditionalOnMissingBean(IFunctionService.class)
publicIFunctionServicefunctionService(ParseFunctionFactoryparseFunctionFactory) {
returnnew DefaultFunctionServiceImpl(parseFunctionFactory);
}
@Bean
public ParseFunctionFactory parseFunctionFactory(@Autowired List<IParseFunction> parseFunctions) {
returnnew ParseFunctionFactory(parseFunctions);
}
@Bean
@ConditionalOnMissingBean(IParseFunction.class)
publicDefaultParseFunctionparseFunction() {
returnnew DefaultParseFunction();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryLogRecordAdvisor logRecordAdvisor(IFunctionService functionService) {
BeanFactoryLogRecordAdvisor advisor =
new BeanFactoryLogRecordAdvisor();
advisor.setLogRecordOperationSource(logRecordOperationSource());
advisor.setAdvice(logRecordInterceptor(functionService));
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public LogRecordInterceptor logRecordInterceptor(IFunctionService functionService) {
LogRecordInterceptor interceptor = new LogRecordInterceptor();
interceptor.setLogRecordOperationSource(logRecordOperationSource());
interceptor.setTenant(enableLogRecord.getString("tenant"));
interceptor.setFunctionService(functionService);
return interceptor;
}
@Bean
@ConditionalOnMissingBean(IOperatorGetService.class)
@Role(BeanDefinition.ROLE_APPLICATION)
publicIOperatorGetServiceoperatorGetService() {
returnnew DefaultOperatorGetServiceImpl();
}
@Bean
@ConditionalOnMissingBean(ILogRecordService.class)
@Role(BeanDefinition.ROLE_APPLICATION)
publicILogRecordServicerecordService() {
returnnew DefaultLogRecordServiceImpl();
}
@Override
publicvoidsetImportMetadata(AnnotationMetadata importMetadata) {
this.enableLogRecord = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableLogRecord.class.getName(), false));
if (this.enableLogRecord == null) {
log.info("@EnableCaching is not present on importing class");
}
}
}
这个类继承 ImportAware 是为了拿到 EnableLogRecord 上的租户属性,这个类使用变量 logRecordAdvisor 和 logRecordInterceptor 装配了 AOP,同时把自定义函数注入到了 logRecordAdvisor 中。对外扩展类:分别是IOperatorGetService
、ILogRecordService
、IParseFunction
。业务可以自己实现相应的接口,因为配置了 @ConditionalOnMissingBean,所以用户的实现类会覆盖组件内的默认实现。
5. 总结
这篇文章介绍了操作日志的常见写法,以及如何让操作日志的实现更加简单、易懂,通过组件的四个模块,介绍了组件的具体实现。对于上面的组件介绍,大家如果有疑问,也欢迎在文末留言,我们会进行答疑。