文章目录
- 前言
- 一、设计 / 对比 / 实现
- 1.1 注解
- 1.1.1 EnableBizLogger
- 1.1.2 BizLogger
- 1.1.3 BizLoggerComponent
- 1.1.4 BizLoggerFunction
- 1.2 自定义函数
- 1.2.1 IBizLoggerFunctionRegistrar
- 1.2.2 AbstractBizLoggerFunctionResolver
- 1.3 日志上下文
- 1.3.1 BizLoggerContext
- 1.4 SpEL
- 1.4.1 BizLoggerEvaluationContext
- 1.4.2 BizLoggerExpressionEvaluator
- 1.5 解析注解并生成日志对象
- 1.5.1 BizLogDTO
- 1.5.1 IBizLogCreatorsExecutorFactory
- 1.5.2 IBizLogCreatorsExecutor
- 1.5.3 IBizLogCreatorFactory
- 1.5.4 IBizLogCreator
- 1.6 Aspect
- 1.6.1 BizLoggerAspect
- 1.7 Configuration
- 1.7.1 BizLoggerConfigureSelector
- 1.7.2 BizLoggerAutoConfiguration
- 二、其他杂类
- 2.1 services
- 2.1.1 IBizLoggerPerformanceMonitor
- 2.1.2 IBizLogOperatorService
- 2.1.3 IBizLogSyncService
- 2.1.4 BizLoggerProperties
前言
美团技术团队曾经发过一篇 如何优雅地记录操作日志?, 简单介绍了业务中为了记录 Who did what at what time
的需求实现, 通过AOP和注解的方式与业务解耦. 遂拜读了下项目源码 mzt-biz-log 和另一个人的一个差异化实现 log-record.
一直以来就想让司内服务有一套清晰的操作日志, 方便业务人员分析客户动态等等. 加上最近这几天其他人负责的服务在出现某些问题需要溯源的时候, 发现其业务操作日志既不可读, 又跟一堆系统日志混在一起. 导致想找到问题原因的时候必须依赖负责服务的开发人员(因为就他知道这是啥, 也许是一句sql, 也许是一行不明所以的log之类的), 十分痛苦.
看过上述的两个项目后, 发现二者其中的代码编写也好, 设计也罢, 让我感觉稍微有点乱, 于是乎带着学习大厂研发的设计的想法, 趁此机会, 自己实现一遍框架功能, 目的并不是简单的把代码抄一遍, 本文将在部分章节具体说明三个框架设计的差异, 优缺点等等.
没时间画图, 直接上代码, 环境: JDK21 + SpringBoot 3.2.1.
一、设计 / 对比 / 实现
包括上述两个框架, 简单来说, 就是以AOP + 注解为基础, 辅以SpEL解析和ThreadLocal的上下文能力, 完成操作日志的配置化, 解耦化.
- mzt-biz-log 考虑兼容性, 选择自行构建并配置AOP组件
- log-record 直接使用注解配置Advice的方式
因为主要用于司内的服务, 所以我也选择直接使用注解配置的方式开启AOP能力
1.1 注解
- EnableBizLogger: 框架自动配置的开关
- BizLoggers: Repeatable, 配置多个BizLogger
- BizLogger: 标记方法, 配置成功日志, 失败日志等, 是记录操作日志的入口
- BizLoggerComponent: 标记自定义函数组件
- BizLoggerFunction: 标记自定义函数, 配合SpEL
1.1.1 EnableBizLogger
添加在配置类或启动类上自动完成框架的装配工作
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BizLoggerConfigureSelector.class)
@EnableSpringUtil
public @interface EnableBizLogger {@MethodDesc("租户信息, 应用或业务隔离")String tenant();AdviceMode mode() default AdviceMode.PROXY;@MethodDesc("创建日志对象执行方式")BizLoggerExecutorType executorType() default BizLoggerExecutorType.SERIAL;@MethodDesc("创建日志对象执行线程池Bean名称")String executorName() default "defaultBizLoggerExecutor";@MethodDesc("使用@BizLoggerComponent/@BizLoggerFunction 自定义函数注册时, 重复函数名称是否覆盖")boolean overrideFunction() default false;enum BizLoggerExecutorType {SERIAL,PARALLEL}
}
1.1.2 BizLogger
利用SpEL表达式, 可配置业务的分类, 日志的可见度, 业务成功日志, 失败日志, 是否记录日志等等
/*** @author hp*/
@Meta
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = BizLoggers.class)
public @interface BizLogger {@Language("SpEL")@MethodDesc("业务序列号,用于追踪数据操作, 如: 订单编号或ID")String bizNo();@Language("SpEL")@MethodDesc("业务类型, 如: 订单")String type();@Language("SpEL")@MethodDesc("业务子类型, 如: 订单 toC, toB端的区分")String subType() default "";@Language("SpEL")@MethodDesc("日志范围, 如: 客户可见, 业务人员可见等区分")String scope() default "";@Language("SpEL")@MethodDesc("业务成功日志模版, 业务正常执行无异常抛出时为成功, 此时上下文呢中可以获取BizLoggerProperties.returnValueKey对应的方法返回值用于构造成功日志")String successLog();@Language("SpEL")@MethodDesc("业务失败日志模版, 业务抛出异常时判断为失败: 此时上下文中可以获取BizLoggerProperties.throwableKey对应的异常信息用于构造异常日志")String errorLog() default "";@Language("SpEL")@MethodDesc("额外信息, 如: 方法请求参数/响应参数等")String extra() default "";@Language("SpEL")@MethodDesc("日志同步条件, 只接受boolean值. 如: true记录日志, false不记录日志")String condition() default "";@MethodDesc("在业务方法执行前/后执行日志记录, 之前=true, 之后=false, 之前场景下, 无法获取业务方法内设置的上下文变量")boolean preInvocation() default false;@MethodDesc("处理/同步顺序")int order() default -1;
}
1.1.3 BizLoggerComponent
通过@Component隐式注册SpringBean, 并在SpringBoot启动时, 根据通知类的接口初始化获取标记类
/*** @author hp*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface BizLoggerComponent {@AliasFor(annotation = Component.class)String value();
}
1.1.4 BizLoggerFunction
SpEL支持在解析上下文中注册自定义变量, 自定义函数, 并在调用时使用#variable
, #customFunction(#param1,#param2)
的格式调用变量或函数.
/*** @author hp*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BizLoggerFunction {String value();
}
1.2 自定义函数
SpEL支持在解析上下文(EvaluationContext)中注册自定义变量setVariable(String name, Object varible)
, 注册自定义函数registerFunction(String name, Method method)
, 并在调用时使用#variable
, #customFunction(#param1,#param2)
的格式调用变量或函数, 但不支持调用非public static
的函数.
如: [#{#order.purchaseName}]下了一个订单. 订单名称: [#{#ORDER_NAME(#order.orderId)}], 购买商品: [#{#order.productName}], 下单结果: [#{#_return}]
其中#ORDER_NAME
就是提前注册的自定义函数名称.
值得一提的是
-
mzt-biz-log 通过定义
接口
的方式, 实现自定义函数, 但受制于接口设计, 自定义函数只能接受一个入参. 同时因为受限于SpEL无法调用非public static
的Function, 导致此框架需要先通过正则自行解析
表达式中所有的自定义函数和入参并自行调用, 再把结果字符串替换掉原始的表达式. 导致设计者不得不增加自定义的表达式格式{{expression}}
, 仅仅为了方便正则解析. 强行引入额外的非SpEL的学习成本, 最终效果就是代码又绕又不清晰, 而且还得维护一堆调用的引用. 使用时又得额外注意其他东西.这样设计, 好处是在非Spring环境也可以使用, 但如果使用环境已经是Spring了, 这样的设计就显得臃肿了.
-
log-record 通过注解+注册SpEL自定义Function的方式, 本文也采用此设计, 但受限于SpEL框架只能注册自定义的
public static
的方法. 调用者只能配置注解public static
方法, 如果配置了非静态方法时, 在注册Function到SpEL上下文时并不会抛出任何异常, 反而是在SpEL表达式对象调用这个自定义函数时抛出异常, 并告知不能调用非静态方法. 对于使用者来说需要提前清楚的东西稍微有点多. 而此框架并未处理此类情况.此框架的实现完全是为了在SpringBoot环境中使用, 这也是我的场景, 所以不需要像原项目那样考虑很多东西.
其次
虽然调用非静态方法的方式也有, 比如将方法所在类的对象设置为SpEL上下文中的变量, 再通过#variable.method(#param1, #param2)
的方式调用; 亦或者将方法类注册SpringBean后, 通过@beanName.method(#param1, #param2)
调用;
静态方法也可以直接通过使用T(全类名).method(#param1, #param2)
调用;
上述几种方式虽然表达式不同, 但显得mzt-biz-log
和log-record
的自定义函数设计不够完善, 当然, 这都是取舍的结果…
所以本次实现时, 结合两个框架的优点, 实现调用者使用方式统一的情况下, 支持自定义非public static
函数的调用.
通过@BizLoggerComponent
的隐式注册SpringBean(log-record的做法), 并在注册自定义函数时首先区分是否静态, 避免SpEL表达式上下文中存在非静态自定义函数从而导致调用异常, 其次, 在把字符串表达式提交给SpEL解析器解析之前, 通过正则提取自定义函数表达式#function()
的函数名称(mzt-biz-log的做法), 并在框架的自定义函数注册器中检查是否为非静态函数, 如果是, 则将字符串表达式中的#function
替换为 @beanName.method
. 完成替换后提交解析. 此时自定义函数的参数列表大小不受限制, 使用者无需关心函数是否静态, 并且在表达式的编写上也可以直接使用 #function()
, 不引入新的规范的同时, 减少了使用者需要了解的规范.
1.2.1 IBizLoggerFunctionRegistrar
在服务启动后扫描并加载自定义函数
/*** @author hp*/
public interface IBizLoggerFunctionRegistrar extends ApplicationContextAware {void registerFunctions(EvaluationContext evaluationContext);
}
直接继承加载能力, 方法主要工作就是将自定义函数名称提取, 并判断是否为非静态函数, 并替换为bean引用的格式
/*** @author hp*/
public interface IBizLoggerFunctionResolver extends IBizLoggerFunctionRegistrar {String resolveFunctions(String expression);
}
1.2.2 AbstractBizLoggerFunctionResolver
上述接口的基础的实现, 并根据@EnableBizLogger.overrideFunction
配置, 扫描时根据Bean顺序处理函数覆盖逻辑.
/*** @author hp*/
@Slf4j
public abstract class AbstractBizLoggerFunctionResolver implements IBizLoggerFunctionResolver {private final static Map<String, MethodWrapper> BIZ_LOG_FUNCTION_HOLDER = Maps.newHashMap();private final static Pattern functionLocator = Pattern.compile("#\\{\\s*(#\\w*)\\(\\s*(.*?)\\)}");protected final String pathJoin = StrUtil.DOT;private final String spELBeanReferencePrefix = StrUtil.AT;protected final boolean overrideFunction;public AbstractBizLoggerFunctionResolver(boolean overrideFunction) {this.overrideFunction = overrideFunction;}@Overridepublic void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {final Map<String, Object> components = applicationContext.getBeansWithAnnotation(BizLoggerComponent.class);if (MapUtil.isEmpty(components)) {log.warn("No custom biz log functions found, consider annotating classes with the @BizLoggerComponent.");}scanFunctions(components);}@Overridepublic void registerFunctions(EvaluationContext evaluationContext) {if (evaluationContext instanceof StandardEvaluationContext sec) {BIZ_LOG_FUNCTION_HOLDER.forEach((name, methodWrapper) -> {if (methodWrapper.isStatic()) {sec.registerFunction(name, methodWrapper.method());}});return;}log.warn("The {} provided is not a StandardEvaluationContext, Can not register functions on it.", evaluationContext.getClass());}@Overridepublic String resolveFunctions(String expression) {final Matcher matcher = functionLocator.matcher(expression);while (matcher.find()) {String functionName = matcher.group(1);final String actualName = functionName.substring(1);if (BIZ_LOG_FUNCTION_HOLDER.containsKey(actualName)) {final MethodWrapper methodWrapper = BIZ_LOG_FUNCTION_HOLDER.get(actualName);if (methodWrapper.isStatic()) {continue;}final String methodReference = spELBeanReferencePrefix + methodWrapper.beanName + pathJoin + methodWrapper.method().getName();expression = expression.replace(functionName, methodReference);}}return expression;}protected void scanFunctions(Map<String, Object> components) {components.forEach((name, component) -> {final Method[] methods = component.getClass().getMethods();if (ArrayUtil.isEmpty(methods)) {return;}final BizLoggerComponent bizLoggerComponent = AnnotatedElementUtils.getMergedAnnotation(component.getClass(), BizLoggerComponent.class);assert bizLoggerComponent != null;final String beanName = bizLoggerComponent.value();Arrays.stream(methods).forEach(method -> {final BizLoggerFunction bizLogFunction = AnnotationUtils.findAnnotation(method, BizLoggerFunction.class);if (Objects.isNull(bizLogFunction)) {return;}putFunction(beanName, bizLogFunction.value(), method);});});}protected void putFunction(String beanName, String methodName, Method method) {Preconditions.checkArgument(StrUtil.isNotEmpty(methodName), "Function name cant be empty.");Preconditions.checkArgument(Objects.nonNull(method), "Function cant be empty.");final MethodWrapper methodWrapper = new MethodWrapper(method, ModifierUtil.isStatic(method), beanName);if (overrideFunction) {BIZ_LOG_FUNCTION_HOLDER.put(methodName, methodWrapper);} else {BIZ_LOG_FUNCTION_HOLDER.putIfAbsent(methodName, methodWrapper);}}private record MethodWrapper(Method method, boolean isStatic, String beanName) { public MethodWrapper {Preconditions.checkArgument(Objects.nonNull(method), "The custom function method cant be null.");Preconditions.checkArgument(StrUtil.isNotEmpty(beanName), "The custom function class name or bean name cant be null.");}}
}
1.3 日志上下文
transmittable-thread-local 是阿里针对InheritableThreadLocal
无法应对池化场景的增强设计. 具体细节这里不展开了, 直接看仓库能学到很多.
利用ThreadLocal, 构建针对全局, 线程, 方法层面的上下文变量, 使得自定义变量可以被解耦的日志切面流程获取到, 进而用于解析SpEL和构建日志数据.
结合上述两个项目对这个上下文的使用, 也是有点乱. 本文构建了基于全局 metaContextHolder
, 基于主线程 threadBasedVariableHolder
, 基于线程方法的 methodBasedVariableHolder
的三个容器.
- mzt-biz-log 采用
InheritableThreadLocal
保存变量, 无法应对池化场景 - log-record 则是使用
TransmittableThreadLocal
但是直接保存StandardEvaluationContext
, 无法应对开辟子线程中上下文变量污染问题.
本文则结合上述两者的优点进行实现.
1.3.1 BizLoggerContext
- 全局容器用于记录元数据, 比如框架装配时拿到的租户信息
- 线程级别用于记录整个链路都可以使用的变量
- 方法级别则针对开辟子线程时, 避免方法内设置的变量互相污染的问题.
/*** @author hp*/
@Slf4j
public class BizLoggerContext {private static final Map<String, Object> metaContextHolder = new ConcurrentHashMap<>(64);// 应对线程池化的场景, 方法级别private static final TransmittableThreadLocal<Deque<Map<String, Object>>> methodBasedVariableHolder = new TransmittableThreadLocal<>();// 应对线程池化的场景, 线程级别private static final TransmittableThreadLocal<Map<String, Object>> threadBasedVariableHolder = new TransmittableThreadLocal<>();private static final String TENANT_KEY = "_tenant";public static void addEmptyFrame() {Optional.ofNullable(methodBasedVariableHolder.get()).ifPresentOrElse(deque -> deque.push(Maps.newHashMap()),() -> {Deque<Map<String, Object>> deque = Queues.newArrayDeque();deque.push(Maps.newHashMap());methodBasedVariableHolder.set(deque);});}public static void putVariable(String key, Object value) {Optional.ofNullable(methodBasedVariableHolder.get()).ifPresentOrElse(deque -> {if (deque.isEmpty()) {deque.push(Maps.newHashMap());}deque.element().put(key, value);},() -> {Deque<Map<String, Object>> deque = Queues.newArrayDeque();deque.push(Maps.newHashMap());methodBasedVariableHolder.set(deque);deque.element().put(key, value);});}public static void putGlobalVariable(String key, Object value) {Optional.ofNullable(threadBasedVariableHolder.get()).ifPresentOrElse(map -> map.put(key, value),() -> {final Map<String, Object> map = Maps.newConcurrentMap();threadBasedVariableHolder.set(map);map.put(key, value);});}public static boolean notEmpty() {return MapUtil.isNotEmpty(metaContextHolder) ||MapUtil.isNotEmpty(threadBasedVariableHolder.get()) ||(Objects.nonNull(methodBasedVariableHolder.get()) && MapUtil.isNotEmpty(methodBasedVariableHolder.get().peek()));}public static Map<String, Object> getAllVariables() {final Map<String, Object> variables = Maps.newHashMap(getGlobalVariables());variables.putAll(getVariables());variables.putAll(metaContextHolder);return variables;}public static Map<String, Object> getVariables() {return Optional.ofNullable(methodBasedVariableHolder.get()).map(Deque::peek).orElse(Maps.newHashMap());}public static Object getVariable(String name) {return getVariables().getOrDefault(name, null);}public static Map<String, Object> getGlobalVariables() {return Optional.ofNullable(threadBasedVariableHolder.get()).map(MapUtil::unmodifiable).orElse(Maps.newHashMap());}public static Object getGlobalVariable(String name) {return getGlobalVariables().getOrDefault(name, null);}public static Object getVariableOrGlobal(String name) {return Optional.ofNullable(getVariable(name)).orElse(getGlobalVariable(name));}public static void clear() {Optional.ofNullable(methodBasedVariableHolder.get()).ifPresent(Deque::pop);}public static void clearGlobal() {Optional.ofNullable(threadBasedVariableHolder.get()).ifPresent(Map::clear);}public static void putTenant(String tenant) {Preconditions.checkArgument(StrUtil.isNotEmpty(tenant), "Biz logger tenant cant be empty.");metaContextHolder.put(TENANT_KEY, tenant);log.info("Biz logger tenant has been set as [{}].", tenant);}public static String getTenant() {return (String) metaContextHolder.getOrDefault(TENANT_KEY, null);}public static void putReturnValue(Object object) {final String returnValueKey = SpringUtil.getBean(BizLoggerProperties.class).getReturnValueKey();putVariable(returnValueKey, object);log.debug("The method invocation return value has been set as {}", object);}public static Object getReturnValue() {final String returnValueKey = SpringUtil.getBean(BizLoggerProperties.class).getReturnValueKey();return getVariable(returnValueKey);}public static void putThrowable(Throwable throwable) {final String throwableKey = SpringUtil.getBean(BizLoggerProperties.class).getThrowableKey();putVariable(throwableKey, throwable);log.debug("The method invocation has thrown an exception of {}.", throwable.getMessage());}public static Throwable getThrowable() {final String throwableKey = SpringUtil.getBean(BizLoggerProperties.class).getThrowableKey();return (Throwable) getVariable(throwableKey);}public static void putTimeCost(Long milliseconds) {final String timeCostKey = SpringUtil.getBean(BizLoggerProperties.class).getTimeCostKey();putVariable(timeCostKey, milliseconds);log.debug("The method invocation costs {}ms.", milliseconds);}public static Long getTimeCost() {final String timeCostKey = SpringUtil.getBean(BizLoggerProperties.class).getTimeCostKey();return (Long) getVariable(timeCostKey);}
}
1.4 SpEL
不同于Join框架的特点, @BizLogger
需要解析方法的入参和对应的参数名称. 此时, 仅仅使用StandardEvaluationContext
已经不能满足处理需求, 虽然JDK17等后续版本对反射的做了更严格限制, 但是SpEL还是提供了MethodBasedEvaluationContext
, 其中包含了ParameterNameDiscoverer
用于解析入参和入参的名称.
可以从MethodBasedEvaluationContext
中看出默认将方法入参以a
或p
开头, 拼接参数列表索引, 如a0
,p1
的方式, 将方法入参设置为SpEL上下文的自定义变量, 方便获取. 实测SpringBoot3.2.1+JDK21使用此方式, 还是可以将参数名称正确解析.
- mzt-biz-log 采用上述方式实现
- log-record 则直接使用
StandardEvaluationContext
, 导致其必须自主解析方法入参, 并仅使用p
+参数列表索引的方式引用方法入参.
本文则实现MethodBasedEvaluationContext
把规范解析入参这部分工作交给SpEL框架.
1.4.1 BizLoggerEvaluationContext
在构造本次方法的上下文时, 将日志上下文中已有的各级变量按顺序设置上.
/*** @author hp*/
public class BizLoggerEvaluationContext extends MethodBasedEvaluationContext {public BizLoggerEvaluationContext(Object rootObject,Method method,Object[] arguments,ParameterNameDiscoverer parameterNameDiscoverer) {super(rootObject, method, arguments, parameterNameDiscoverer);if (BizLoggerContext.notEmpty()) {BizLoggerContext.getAllVariables().forEach(this::setVariable);}}
}
1.4.2 BizLoggerExpressionEvaluator
相比于常用的SpelExpressionParser
, CachedExpressionEvaluator
主要是对表达式解析做了缓存处理.
- mzt-biz-log: 虽然采用
CachedExpressionEvaluator
增加了缓存操作, 但是因为其引入了自定义格式{{}}
, 导致解析繁琐, 多了自主正则提取的再用SpEL解析, 再将解析结果替换原内容的操作. - log-record: 则更加直接, 因为框架测试类里也仅配置
public static
函数, SpEL也仅支持调用自定义的public static
函数, 所以直接用SpelExpressionParser
解析并获取解析结果.
本文则针对CachedExpressionEvaluator
的实现重载其中的解析方法, 增加模版上下文能力ParserContext
(SpEL标准组件), 提供默认模版, 这就是SpEL文档最后提到的模版解析expressions-templating.
支持仅解析#{}
内部的内容, 模版以外的内容将作为字符串保留.
如: [#{#order.purchaseName}]下了一个订单. 订单名称: [#{#ORDER_NAME(#order.orderId)}], 购买商品: [#{#order.productName}], 下单结果: [#{#_return}].
/*** @author hp*/
@Slf4j
public class BizLoggerExpressionEvaluator extends CachedExpressionEvaluator {private final Map<ExpressionKey, Expression> expressionCache = Maps.newConcurrentMap();private final ParserContext parserContext = ParserContext.TEMPLATE_EXPRESSION;private final ExpressionParser expressionParser;private final IBizLoggerFunctionResolver bizLoggerFunctionResolver;public BizLoggerExpressionEvaluator(SpelExpressionParser expressionParser) {super(expressionParser);this.expressionParser = expressionParser;this.bizLoggerFunctionResolver = SpringUtil.getBean(IBizLoggerFunctionResolver.class);}public <T> T parseExpression(String expression, Class<?> targetClass, Method method, EvaluationContext evaluationContext) {if (StrUtil.isEmpty(expression)) {return null;}return parseExpression(expression, new AnnotatedElementKey(method, targetClass), evaluationContext);}@SuppressWarnings("unchecked")protected <T> T parseExpression(String expression, AnnotatedElementKey methodKey, EvaluationContext evaluationContext) {final String resolvedExpression = bizLoggerFunctionResolver.resolveFunctions(expression);log.debug("The expression being resolved is {}", resolvedExpression);return (T) getExpression(this.expressionCache, methodKey, resolvedExpression).getValue(evaluationContext);}@Nonnull@Overrideprotected Expression parseExpression(@Nonnull String expression) {return expressionParser.parseExpression(expression, parserContext);}
}
1.5 解析注解并生成日志对象
1.5.1 BizLogDTO
上述两个框架这里的设计都大同小异.
- mzt-biz-log: 通过自定义函数能力, 提供了一个
_Diff
函数用于记录属性值变化 - log-record: 基本和上述相同. 只是在调用函数, 和对函数结果的处理上不一样而已.
本文V1版本仅实现了操作日志, DiffLog功能将在V2中实现. 因为上述两个的_Diff
也都挺乱的.
/*** @author hp*/
@Data
@Builder
public class BizLogDTO {private String tenant;private String bizNo;private String type;private String subType;private String scope;private String action;private BizLogOperator operator;private Instant operatedAt;private String extra;private boolean condition;private boolean succeed;private Long timeCost;// private List<DiffLog> diffs;public void succeed(String action) {this.succeed = true;this.action = action;}public void failed(String action) {this.succeed = false;this.action = action;}
}
1.5.1 IBizLogCreatorsExecutorFactory
IBizLogCreatorsExecutorFactory
在本文是通过注解中的表达式, 构建日志对象的唯一入口. 本节主要是说明这个流程.
- mzt-biz-log: 源码中则是零散的对表达式提取, 再根据注解的映射关系, 对应解析结果, 不仅代码杂乱, 方法的定义上也像是直接通过IDE抽取自动生成的一样.
- log-record: 则是直接事务脚本式的解析并赋值.
上述两者写的切面类都给人一种杂乱, 臃肿的感觉, 并且哪些值要放在日志上下文中传递, 哪些不需要这样做也是很随心所欲. 并且这种通过解析表达式, 同时表达式解析取值时还可能存在额外的方法调用的情况下, 两个框架都是用方法线程串行调用, 可能会比较耗时.
其实在如何解析一个由SpEL表达式构成的注解, 并将解析数据封装为对象的逻辑上, Join框架的factory包已经提供了一个设计模版.
简单来说就是, 工厂负责构建具体如何解析, 并通过解析模版创建解析类, 再根据串并行配置, 编排顺序后统一提交执行解析.
/*** @author hp*/
public interface IBizLogCreatorsExecutorFactory {IBizLogCreatorsExecutor createFor(Class<?> targetClass, Method method);
}/*** @author hp*/
@Slf4j
public class DefaultBizLogCreatorsExecutorFactory implements IBizLogCreatorsExecutorFactory {private final Map<AnnotatedElementKey, IBizLogCreatorsExecutor> cache = Maps.newConcurrentMap();private final AnnotationAttributes enableBizLog;private final List<IBizLogCreatorFactory> bizLogCreatorFactories;private final Map<String, ExecutorService> executorServiceMap;private final BizLoggerExceptionNotifier bizLoggerExceptionNotifier;private final BeanResolver beanResolver;public DefaultBizLogCreatorsExecutorFactory(AnnotationAttributes enableBizLog,List<IBizLogCreatorFactory> bizLogCreatorFactories,Map<String, ExecutorService> executorServiceMap,BizLoggerExceptionNotifier bizLoggerExceptionNotifier,BeanResolver beanResolver) {this.enableBizLog = enableBizLog;this.bizLogCreatorFactories = bizLogCreatorFactories;AnnotationAwareOrderComparator.sort(this.bizLogCreatorFactories);this.executorServiceMap = executorServiceMap;this.bizLoggerExceptionNotifier = bizLoggerExceptionNotifier;this.beanResolver = beanResolver;}@Overridepublic IBizLogCreatorsExecutor createFor(Class<?> targetClass, Method method) {final AnnotatedElementKey key = new AnnotatedElementKey(method, targetClass);return this.cache.computeIfAbsent(key, k -> createCreatorsExecutor(targetClass, method));}private IBizLogCreatorsExecutor createCreatorsExecutor(Class<?> targetClass, Method method) {final List<IBizLogCreator> creators = this.bizLogCreatorFactories.stream().flatMap(factory -> factory.createFor(targetClass, method).stream()).toList();return buildCreatorsExecutor(targetClass, method, enableBizLog, creators);}private IBizLogCreatorsExecutor buildCreatorsExecutor(Class<?> targetClass, Method method, AnnotationAttributes enableBizLog, List<IBizLogCreator> creators) {if (enableBizLog == null || enableBizLog.getEnum("executorType") == EnableBizLogger.BizLoggerExecutorType.SERIAL) {log.debug("Biz Logger for {}.{} uses serial executor", targetClass, method);return new SerialBizLogCreatorExecutor(beanResolver, creators);}final String executorName = enableBizLog.getString("executorName");if (enableBizLog.getEnum("executorType") == EnableBizLogger.BizLoggerExecutorType.PARALLEL) {log.debug("Biz Logger for {}.{} uses parallel executor, the executor pool is {}", targetClass, method, executorName);final ExecutorService executorService = executorServiceMap.get(executorName);Preconditions.checkArgument(executorService != null, "The required executorService=%s doesn't exist.".formatted(executorName));return new ParallelBizLogCreatorExecutor(beanResolver,creators,executorService,bizLoggerExceptionNotifier);}throw new IllegalArgumentException("无效类型");}
}
1.5.2 IBizLogCreatorsExecutor
执行解析器的执行器, 主要作用就是区分串并行任务. 例如如下的 并行执行器.
/*** @author hp*/
public interface IBizLogCreatorsExecutor {Collection<BizLogDTO> execute(MethodInvocationWrapper invocationWrapper, boolean preInvocation);
}/*** @author hp*/
@Slf4j
public class ParallelBizLogCreatorExecutor extends AbstractBizLogCreatorsExecutor {private final ExecutorService executorService;private final BizLoggerExceptionNotifier exceptionNotifier;private final List<ExecutorWithLevel> executorWithLevels;public ParallelBizLogCreatorExecutor(BeanResolver beanResolver,List<IBizLogCreator> creators,ExecutorService executorService,BizLoggerExceptionNotifier exceptionNotifier) {super(beanResolver, creators);this.executorService = executorService;this.exceptionNotifier = exceptionNotifier;this.executorWithLevels = buildExecutorWithLevel();}private List<ExecutorWithLevel> buildExecutorWithLevel() {return getCreators().stream().collect(Collectors.groupingBy(IBizLogCreator::runOnLevel)).entrySet().stream().map(entry -> new ExecutorWithLevel(entry.getKey(), entry.getValue())).sorted(Comparator.comparing(ExecutorWithLevel::level)).collect(Collectors.toList());}@Overridepublic Collection<BizLogDTO> execute(MethodInvocationWrapper invocationWrapper, boolean preInvocation) {final EvaluationContext evaluationContext = BizLoggerEvaluationContextFactory.createEvaluationContext(invocationWrapper.getMethod(),invocationWrapper.getArgs(),invocationWrapper.getTargetClass(),getBeanResolver());return executeCreation(evaluationContext, preInvocation);}private List<BizLogDTO> executeCreation(EvaluationContext evaluationContext, boolean preInvocation) {return this.executorWithLevels.stream().flatMap(leveledTasks -> {log.debug("Run creation on level {} use {}", leveledTasks.level(), leveledTasks.creators());final List<Task> tasks = buildCreationTasks(leveledTasks, evaluationContext, preInvocation);if (CollUtil.isEmpty(tasks)) {return Stream.empty();}try {if (log.isDebugEnabled()) {StopWatch stopwatch = new StopWatch("Starting executing creation tasks");stopwatch.start();final Stream<BizLogDTO> bizLogDTOs = this.executorService.invokeAll(tasks).stream().map(f -> {try {return f.get();} catch (InterruptedException | ExecutionException e) {exceptionNotifier.handle().accept(evaluationContext, e);return null;}}).filter(Objects::nonNull);stopwatch.stop();log.debug("Run execute cost {} ms, task is {}.", stopwatch.getTotalTimeMillis(), tasks);return bizLogDTOs;} else {return this.executorService.invokeAll(tasks).stream().map(f -> {try {return f.get();} catch (InterruptedException | ExecutionException e) {exceptionNotifier.handle().accept(evaluationContext, e);return null;}}).filter(Objects::nonNull);}} catch (InterruptedException e) {throw new BizLoggerException(BizLoggerErrorCode.async_creation_error, e);}}).filter(Objects::nonNull).collect(Collectors.toList());}private List<Task> buildCreationTasks(ExecutorWithLevel leveledExecutors, EvaluationContext evaluationContext, boolean preInvocation) {return leveledExecutors.creators().stream().filter(c -> c.preInvocation() == preInvocation).map(executor -> new Task(executor::createLog, evaluationContext, exceptionNotifier)).collect(Collectors.toList());}@AllArgsConstructorstatic class Task implements Callable<BizLogDTO> {private final Function<EvaluationContext, BizLogDTO> function;private final EvaluationContext evaluationContext;private final BizLoggerExceptionNotifier exceptionNotifier;@Overridepublic BizLogDTO call() {try {return function.apply(evaluationContext);} catch (Exception e) {exceptionNotifier.handle().accept(evaluationContext, e);}return null;}}record ExecutorWithLevel(Integer level, List<IBizLogCreator> creators) {}
}
1.5.3 IBizLogCreatorFactory
通过工厂创建日志对象的处理器, 处理器的方法, 则由工厂定义, 处理器只负责编排和调用, 不关心方法具体逻辑.
/*** @author hp*/
public interface IBizLogCreatorFactory {List<IBizLogCreator> createFor(Class<?> targetClass, Method method);
}
/*** @author hp*/
public abstract class AbstractAnnotationBasedBizLogCreatorFactory<A extends Annotation> implements IBizLogCreatorFactory {protected final Class<A> annotationClass;public AbstractAnnotationBasedBizLogCreatorFactory(Class<A> annotationClass) {this.annotationClass = annotationClass;}@Overridepublic List<IBizLogCreator> createFor(Class<?> targetClass, Method method) {return createBizLoggerExecutors(targetClass, method);}protected List<IBizLogCreator> createBizLoggerExecutors(Class<?> targetClass, Method method) {if (!AnnotatedElementUtils.isAnnotated(method, annotationClass)) {return Collections.emptyList();}final Set<A> annotations = AnnotatedElementUtils.findMergedRepeatableAnnotations(method, annotationClass);return annotations.stream().map(annotation -> createBizLoggerExecutor(targetClass, method, annotation)).toList();}protected IBizLogCreator createBizLoggerExecutor(Class<?> targetClass, Method method, A annotation) {return new DefaultBizLogCreator(createForOrder(targetClass, method, annotation),createForPreInvocation(targetClass, method, annotation),createForBizNo(targetClass, method, annotation),createForType(targetClass, method, annotation),createForSubType(targetClass, method, annotation),createForScope(targetClass, method, annotation),createForOperator(targetClass, method, annotation),createForSuccessLog(targetClass, method, annotation),createForErrorLog(targetClass, method, annotation),createForExtra(targetClass, method, annotation),createForCondition(targetClass, method, annotation));}protected abstract int createForOrder(Class<?> targetClass, Method method, A annotation);protected abstract boolean createForPreInvocation(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForBizNo(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForType(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForSubType(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForScope(Class<?> targetClass, Method method, A annotation);protected abstract BizLogOperator createForOperator(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForSuccessLog(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForErrorLog(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, String> createForExtra(Class<?> targetClass, Method method, A annotation);protected abstract Function<EvaluationContext, Boolean> createForCondition(Class<?> targetClass, Method method, A annotation);
}
解析@BizLogger
注解的工厂实现
/*** @author hp*/
@Slf4j
public class BizLoggerBasedBizLogCreatorFactory extends AbstractAnnotationBasedBizLogCreatorFactory<BizLogger> {private final BizLoggerExpressionEvaluator bizLoggerExpressionEvaluator = new BizLoggerExpressionEvaluator(new SpelExpressionParser());private final IBizLogOperatorService bizLogOperatorService;public BizLoggerBasedBizLogCreatorFactory(ObjectProvider<IBizLogOperatorService> bizLogOperatorService) {super(BizLogger.class);this.bizLogOperatorService = bizLogOperatorService.getIfAvailable();}@Overrideprotected int createForOrder(Class<?> targetClass, Method method, BizLogger annotation) {return annotation.order();}@Overrideprotected boolean createForPreInvocation(Class<?> targetClass, Method method, BizLogger annotation) {return annotation.preInvocation();}@Overrideprotected Function<EvaluationContext, String> createForBizNo(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.bizNo(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, String> createForType(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.type(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, String> createForSubType(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.subType(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, String> createForScope(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.scope(), targetClass, method, context);}@Overrideprotected BizLogOperator createForOperator(Class<?> targetClass, Method method, BizLogger annotation) {return bizLogOperatorService.get();}@Overrideprotected Function<EvaluationContext, String> createForSuccessLog(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.successLog(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, String> createForErrorLog(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.errorLog(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, String> createForExtra(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.extra(), targetClass, method, context);}@Overrideprotected Function<EvaluationContext, Boolean> createForCondition(Class<?> targetClass, Method method, BizLogger annotation) {return context -> bizLoggerExpressionEvaluator.parseExpression(annotation.condition(), targetClass, method, context);}
}
1.5.4 IBizLogCreator
仅仅关心方法的编排和调用, 方法逻辑由工厂提供.
/*** @author hp*/
public interface IBizLogCreator {BizLogDTO createLog(EvaluationContext evaluationContext);default boolean preInvocation() {return false;}default int runOnLevel() {return 0;}
}
/*** @author hp*/
public abstract class AbstractBizLogCreator implements IBizLogCreator {protected abstract String bizNo(EvaluationContext evaluationContext);protected abstract String type(EvaluationContext evaluationContext);protected abstract String subType(EvaluationContext evaluationContext);protected abstract String scope(EvaluationContext evaluationContext);protected abstract BizLogOperator operator();protected abstract String successLog(EvaluationContext evaluationContext);protected abstract String errorLog(EvaluationContext evaluationContext);protected abstract String extra(EvaluationContext evaluationContext);protected abstract boolean condition(EvaluationContext evaluationContext);@Overridepublic BizLogDTO createLog(EvaluationContext evaluationContext) {final BizLogDTO bizLogDTO = BizLogDTO.builder().tenant(BizLoggerContext.getTenant()).bizNo(bizNo(evaluationContext)).type(type(evaluationContext)).subType(subType(evaluationContext)).scope(scope(evaluationContext)).operator(operator()).operatedAt(Instant.now()).timeCost(BizLoggerContext.getTimeCost()).condition(condition(evaluationContext)).extra(extra(evaluationContext)).build();Optional.ofNullable(BizLoggerContext.getThrowable()).ifPresentOrElse(thr -> bizLogDTO.failed(errorLog(evaluationContext)),() -> bizLogDTO.succeed(successLog(evaluationContext)));return bizLogDTO;}
}
1.6 Aspect
在切面设计上
- mzt-biz-log: 为了兼容性, 自主实现切面相关组件和配置. 使用一个wrapper类处理业务的调用, 这个很不戳.
- log-record: 本身就是为了SpringBoot环境构建的, 直接使用注解配置. 类似
mzt-biz-log
, 它也增加了Stopwatch的记录, 但代码整体看着还是比较乱, 因为它全塞到一个方法里了, 方法超级长.
本文也是通过注解配置实现切面. 总体来说实现上log-record
做的更简单易懂, 但设计没mzt-biz-log
好.
1.6.1 BizLoggerAspect
同样的使用了一个wrapper类封装业务方法的调用和异常捕获.
MethodInvocationWrapper
- mzt-biz-log: 用法偏贫血模型那样
- log-record: 没这设计
本文更偏向充血的方向, 将逻辑内聚一些.
/*** @author hp*/
public class MethodInvocationWrapper {private final ProceedingJoinPoint joinPoint;private final StopWatch stopWatch;@Getterprivate final Class<?> targetClass;@Getterprivate final Method method;@Getterprivate final Object[] args;private boolean success = false;@Getterprivate Object result = null;@Getterprivate Throwable throwable;public MethodInvocationWrapper(ProceedingJoinPoint joinPoint, StopWatch stopWatch) {Preconditions.checkArgument(Objects.nonNull(joinPoint), "The joinPoint cant be null.");Preconditions.checkArgument(Objects.nonNull(stopWatch), "The stopWatch cant be null.");Preconditions.checkArgument(joinPoint.getSignature() instanceof MethodSignature, "The ProceedingJoinPoint has to be a MethodSignature type.");this.joinPoint = joinPoint;this.targetClass = getTargetClass(joinPoint.getTarget());this.method = ((MethodSignature) joinPoint.getSignature()).getMethod();this.args = joinPoint.getArgs();this.stopWatch = stopWatch;if (stopWatch.isRunning()) {stopWatch.stop();}}public void proceed() {stopWatch.start(IBizLoggerPerformanceMonitor.MONITOR_TASK_INVOCATION);try {this.result = joinPoint.proceed();this.success = true;BizLoggerContext.putReturnValue(this.result);} catch (Throwable e) {this.success = false;this.throwable = e;BizLoggerContext.putThrowable(this.throwable);}stopWatch.stop();BizLoggerContext.putTimeCost(stopWatch.lastTaskInfo().getTimeMillis());}public boolean failed() {return Objects.nonNull(this.throwable) || !this.success;}private static Class<?> getTargetClass(Object target) {return AopProxyUtils.ultimateTargetClass(target);}public void throwException() throws Throwable {if (Objects.isNull(this.throwable)) {return;}throw this.throwable;}
}
BizLoggerAspect
-
mzt-biz-log: 你就看吧, 一看一个不吱声;
同步日志的逻辑, 通过预留接口, 把实现交给使用方, 并通过方法线程调用.
-
log-record: 你就看吧, 一看一个不吱声;
同步日志的逻辑, 考虑了串并行方式, 直接内聚在切面中, 并在实现上支持多种管道. 提供了常用消息队列的配置(都是最基本配置, 到手大概率还得自定义)等等.
本文在构建日志对象BizLogDTO
的逻辑上, 因为引入工厂模式, 收敛了入口, 整体代码会少些. 同步方式目前也只是通过方法线程简单调用接口, 实现留给使用方, 后续可能会调整, 上述工厂设计(串并行执行器部分)可以应用到这里.
/*** @author hp*/
@Slf4j
@Aspect
@RequiredArgsConstructor
public class BizLoggerAspect {private final ObjectProvider<IBizLoggerPerformanceMonitor> bizLogPerformanceMonitor;private final ObjectProvider<IBizLogCreatorsExecutorFactory> bizLogCreatorsExecutorFactory;private final ObjectProvider<IBizLogSyncService> bizLogSyncService;@Around("@annotation(com.luban.biz.logger.annotation.BizLoggers) || @annotation(com.luban.biz.logger.annotation.BizLogger)")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {BizLoggerContext.addEmptyFrame();final StopWatch stopWatch = new StopWatch(MONITOR_NAME);final MethodInvocationWrapper invocationWrapper = new MethodInvocationWrapper(joinPoint, stopWatch);final Collection<BizLogDTO> preInvocationLogs = preInvocation(invocationWrapper, stopWatch);invocationWrapper.proceed();final Collection<BizLogDTO> postInvocationLogs = postInvocation(invocationWrapper, stopWatch);syncLogs(preInvocationLogs, postInvocationLogs, stopWatch);if (invocationWrapper.failed()) {invocationWrapper.throwException();}return invocationWrapper.getResult();}private void syncLogs(Collection<BizLogDTO> preInvocationLogs, Collection<BizLogDTO> postInvocationLogs, StopWatch stopWatch) {if (stopWatch.isRunning()) {stopWatch.stop();}final List<BizLogDTO> logs = Lists.newArrayList(preInvocationLogs);logs.addAll(postInvocationLogs);stopWatch.start(MONITOR_TASK_SYNC_LOGS);try {Optional.ofNullable(bizLogSyncService.getIfAvailable()).ifPresent(service -> service.sync(logs));} catch (Exception e) {log.error("BizLoggerAspect.doAround - syncLogs - Sync logs failed:", e);} finally {stopWatch.stop();}}private Collection<BizLogDTO> preInvocation(MethodInvocationWrapper invocationWrapper, StopWatch stopWatch) {if (stopWatch.isRunning()) {stopWatch.stop();}stopWatch.start(MONITOR_TASK_BEFORE_INVOCATION);Collection<BizLogDTO> bizLogDTOs = Collections.emptyList();try {bizLogDTOs = Optional.ofNullable(bizLogCreatorsExecutorFactory.getIfAvailable()).map(factory -> factory.createFor(invocationWrapper.getTargetClass(), invocationWrapper.getMethod())).map(executor -> executor.execute(invocationWrapper, true)).orElse(Collections.emptyList());} catch (Exception e) {log.error("BizLoggerAspect.doAround - preInvocation - Creating BizLogCreators failed:", e);} finally {stopWatch.stop();}return bizLogDTOs;}private Collection<BizLogDTO> postInvocation(MethodInvocationWrapper invocationWrapper, StopWatch stopWatch) {if (stopWatch.isRunning()) {stopWatch.stop();}stopWatch.start(MONITOR_TASK_AFTER_INVOCATION);Collection<BizLogDTO> bizLogDTOs = Collections.emptyList();try {bizLogDTOs = Optional.ofNullable(bizLogCreatorsExecutorFactory.getIfAvailable()).map(factory -> factory.createFor(invocationWrapper.getTargetClass(), invocationWrapper.getMethod())).map(executor -> executor.execute(invocationWrapper, false)).orElse(Collections.emptyList());} catch (Exception e) {log.error("BizLoggerAspect.doAround - postInvocation - Creating BizLogCreators failed:", e);} finally {stopWatch.stop();}try {Optional.ofNullable(this.bizLogPerformanceMonitor.getIfAvailable()).ifPresent(monitor -> monitor.print(stopWatch));} catch (Exception e) {log.error("BizLoggerAspect.doAround - postInvocation - Monitoring biz logger performance failed:", e);}return bizLogDTOs;}
}
1.7 Configuration
加载方式照抄@EnableTransactionManagement
1.7.1 BizLoggerConfigureSelector
/*** @author hp*/
public class BizLoggerConfigureSelector extends AdviceModeImportSelector<EnableBizLogger> {@Overrideprotected String[] selectImports(AdviceMode adviceMode) {return switch (adviceMode) {case PROXY -> new String[]{AutoProxyRegistrar.class.getName(), BizLoggerAutoConfiguration.class.getName()};case ASPECTJ -> new String[]{BizLoggerAutoConfiguration.class.getName()};};}
}
1.7.2 BizLoggerAutoConfiguration
- mzt-biz-log: 使用
@Enablexxx
注解设计, 基本上就是照搬@EnableTransactionManagement
- log-record: 使用Spring的imports文件, 涉及SpringBoot高低版本对imports文件格式要求不一致的问题, 所以导致它需要提供SpringBoot3x和2x的插件包.
本文采用mzt-biz-log
的设计, 相对灵活些.
/*** @author hp*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@Import(BizLoggerProperties.class)
public class BizLoggerAutoConfiguration implements ImportAware {private AnnotationAttributes enableBizLog;@Overridepublic void setImportMetadata(AnnotationMetadata importMetadata) {this.enableBizLog = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableBizLogger.class.getName(), false));if (this.enableBizLog == null) {log.info("Annotate the spring boot application class with the @EnableBizLog to enable the biz logger.");}initMetaBizLogContext();}private void initMetaBizLogContext() {BizLoggerContext.putTenant(enableBizLog.getString("tenant"));}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BizLoggerAspect bizLogAspect(ObjectProvider<IBizLoggerPerformanceMonitor> bizLogPerformanceMonitor,ObjectProvider<IBizLogCreatorsExecutorFactory> bizLogCreatorsExecutorFactory,ObjectProvider<IBizLogSyncService> bizLogSyncService) {return new BizLoggerAspect(bizLogPerformanceMonitor,bizLogCreatorsExecutorFactory,bizLogSyncService);}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnMissingBean(IBizLogCreatorsExecutorFactory.class)public IBizLogCreatorsExecutorFactory bizLogCreatorsExecutorFactory(List<IBizLogCreatorFactory> bizLogCreatorFactories,Map<String, ExecutorService> executorServiceMap,BizLoggerExceptionNotifier bizLoggerExceptionNotifier,BeanFactory beanFactory) {return new DefaultBizLogCreatorsExecutorFactory(enableBizLog,bizLogCreatorFactories,executorServiceMap,bizLoggerExceptionNotifier,new BeanFactoryResolver(beanFactory));}@Bean@Role(BeanDefinition.ROLE_SUPPORT)public ExecutorService defaultBizLoggerExecutor() {final BasicThreadFactory basicThreadFactory = new BasicThreadFactory.Builder().namingPattern("BizLogger-Thread-%d").daemon(true).build();int maxSize = Runtime.getRuntime().availableProcessors();return new ThreadPoolExecutor(0,maxSize,60L,TimeUnit.SECONDS,new SynchronousQueue<>(),basicThreadFactory,new ThreadPoolExecutor.CallerRunsPolicy());}@Bean@ConditionalOnMissingBean(BizLoggerExceptionNotifier.class)public BizLoggerExceptionNotifier bizLoggerExceptionNotifier() {return () -> (data, ex) -> {log.error("Biz Logger Exception: ", ex);log.error("Exception context={}", data);};}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnMissingBean(IBizLogCreatorFactory.class)public IBizLogCreatorFactory bizLogCreatorFactory(ObjectProvider<IBizLogOperatorService> bizLogOperatorService) {return new BizLoggerBasedBizLogCreatorFactory(bizLogOperatorService);}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnMissingBean(IBizLoggerFunctionRegistrar.class)public IBizLoggerFunctionResolver bizLoggerFunctionResolver() {return new AbstractBizLoggerFunctionResolver(enableBizLog.getBoolean("overrideFunction")) {};}@Bean@Role(BeanDefinition.ROLE_APPLICATION)@ConditionalOnMissingBean(IBizLogOperatorService.class)public IBizLogOperatorService bizLogOperatorService() {return () -> new BizLogOperator("","BizLogger-Default-Operator");}@Bean@Role(BeanDefinition.ROLE_SUPPORT)@ConditionalOnMissingBean(IBizLoggerPerformanceMonitor.class)public IBizLoggerPerformanceMonitor bizLogPerformanceMonitor() {return stopWatch -> log.debug("BizLogger performance: {}", stopWatch.prettyPrint(TimeUnit.NANOSECONDS));}@Bean@Role(BeanDefinition.ROLE_SUPPORT)@ConditionalOnMissingBean(ObjectMapper.class)public ObjectMapper objectMapper() {return JsonMapper.builder().defaultPrettyPrinter(new DefaultPrettyPrinter()).addModule(new JavaTimeModule()).build();}@Bean@Role(BeanDefinition.ROLE_APPLICATION)@ConditionalOnMissingBean(IBizLogSyncService.class)public IBizLogSyncService bizLogSyncService(ObjectMapper objectMapper) {return logs -> {if (CollUtil.isEmpty(logs)) {return;}logs.forEach(bizLog -> {try {log.info("{}", objectMapper.writeValueAsString(bizLog));} catch (JsonProcessingException e) {log.error("Biz Logger - Default bizLogSyncService failed: ", e);}});};}
}
二、其他杂类
2.1 services
2.1.1 IBizLoggerPerformanceMonitor
- mzt-biz-log: 原本的设计
- log-record: 事务脚本
本文同mzt-biz-log
设计
/*** @author hp*/
@FunctionalInterface
public interface IBizLoggerPerformanceMonitor {String MONITOR_NAME = "biz-logger-performance";String MONITOR_TASK_SYNC_LOGS = "sync-logs";String MONITOR_TASK_BEFORE_INVOCATION = "before-invocation";String MONITOR_TASK_INVOCATION = "invocation";String MONITOR_TASK_AFTER_INVOCATION = "after-invocation";void print(StopWatch stopWatch);
}
2.1.2 IBizLogOperatorService
业务操作人, 一般web应用会通过上下文容器存储当前用户信息. 通过该接口获取当前业务操作人信息.
/*** @author hp*/
@FunctionalInterface
public interface IBizLogOperatorService {BizLogOperator get();
}
2.1.3 IBizLogSyncService
同步日志操作, 具体逻辑交给使用者定义, 发消息队列也好, 直接落库也好, 直接log打印也好.
/*** @author hp*/
public interface IBizLogSyncService {void sync(Collection<BizLogDTO> bizLogDTOs);
}
2.1.4 BizLoggerProperties
上述两个框架都提供了方法返回值
和方法抛出的异常
这两个日志上下文变量, 但直接写死.
本文则尝试将这些能被使用者在编写SpEL表达式
时用到的变量作为可配置的. 这仅仅是一种取舍或者公约而已.
/*** @author hp*/
@Data
@ConfigurationProperties(prefix = "biz.logger")
public class BizLoggerProperties {private String returnValueKey = "_return";private String throwableKey = "_throwable";private String timeCostKey = "_timeCost";
}