Biz-Logger操作日志框架

文章目录

  • 前言
  • 一、设计 / 对比 / 实现
    • 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-loglog-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中看出默认将方法入参以ap开头, 拼接参数列表索引, 如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";
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/871290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SSM学习6:Spring事务

简介 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败Spring事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同成功同失败 public interface PlatformTransactionManager{void commit(TransactionStatus status) throws TransactionStatus ;vo…

【网络文明】关注网络安全

在这个数字化时代&#xff0c;互联网已成为我们生活中不可或缺的一部分&#xff0c;它极大地便利了我们的学习、工作、娱乐乃至日常生活。然而&#xff0c;随着网络空间的日益扩大&#xff0c;网络安全问题也日益凸显&#xff0c;成为了一个不可忽视的全球性挑战。认识到网络安…

Python数据分析案例52——基于SSA-LSTM的风速预测(麻雀优化)

案例背景 又要开始更新时间序列水论文的系列的方法了&#xff0c;前面基于各种不同神经网络层&#xff0c;还有注意力机制做了一些缝合模型。 其实论文里面用的多的可能是优化算法和模态分解&#xff0c;这两个我还没出专门的例子&#xff0c;这几天正好出一个优化算法的例子来…

uboot学习:(四)顶层makefile分析

目录 版本号 MAKEFLAGS变量 命令输出 静默输出 设置编译结果输出目录 代码检查&#xff08;一般不需要使用&#xff0c;了解就行&#xff09; 模块编译&#xff08;一般不用uboot编译模块&#xff0c;了解就行&#xff09; 获取主机架构和系统 设置目标架构、交叉编译…

隔离驱动-视频课笔记

目录 1、需要隔离的原因 1.2、四种常用的隔离方案 2、脉冲变压器隔离 2.1、脉冲变压器的工作原理 2.2、泄放电阻对开关电路的影响 2.3、本课小结 3、光耦隔离驱动 3.1、光耦隔离驱动原理 3.2、光耦隔离驱动的电源进行分析 3.3、本课小结 4、自举升压驱动 4.1…

大数据开发中的元数据:从基础到高级应用的全面指南

在大数据开发中&#xff0c;元数据&#xff08;Metadata&#xff09;是指描述数据的数据。元数据可以提供有关数据结构、数据类型、数据约束和数据关系的重要信息。合理利用元数据可以显著提高数据建模和管理的效率。本文将详细介绍如何根据元数据建表&#xff0c;并提供一些代…

2024年公司电脑屏幕监控软件推荐|6款好用的屏幕监控软件盘点!

在当今的商业环境中&#xff0c;确保员工的工作效率和数据安全是每个企业管理者的重要任务。屏幕监控软件通过实时监控和记录员工的电脑活动&#xff0c;帮助企业有效地管理和优化工作流程。 1.固信软件 固信软件https://www.gooxion.com/ 主要特点&#xff1a; 实时屏幕监控…

养殖业饲料加工新选择,粉碎机械提升效率

在当今畜牧业快速发展的时代&#xff0c;饲料加工设备成为提升养殖效益的重要一环。其中&#xff0c;饲料加工粉碎机凭借其G效、便捷的特点&#xff0c;成为了养殖场的得力助手。 饲料加工粉碎机作为养殖业的重要设备之一&#xff0c;其主要功能是将各种原料如玉米、豆粕、麦…

Sentinel限流算法:滑动时间窗算法、漏桶算法、令牌桶算法。拦截器定义资源实现原理

文章目录 滑动时间窗算法基本知识源码算法分析 漏桶算法令牌桶算法拦截器处理web请求 滑动时间窗算法 基本知识 限流算法最简单的实现就是使用一个计数器法。比如对于A接口来说&#xff0c;我要求一分钟之内访问量不能超过100&#xff0c;那么我们就可以这样来实现&#xff1…

(一)高并发压力测试调优篇——MYSQL数据库的调优

前言 在实际项目开发中&#xff0c;很多业务场景下都需要考虑接口的性能要求&#xff0c;追求高并发、高吞吐量。那么对于此类问题如何入手呢&#xff1f;关注作者&#xff0c;不迷路。本节内容主要介绍在数据库db方面的优化&#xff0c;以mysql数据库为例。 关于db的优化&am…

7、matlab实现SGM/BM/SAD立体匹配算法计算视差图

1、matlab实现SGM/BM/SAD立体匹配算法计算视差图简介 SGM&#xff08;Semi-Global Matching&#xff09;、BM&#xff08;Block Matching&#xff09;和SAD&#xff08;Sum of Absolute Differences&#xff09;都是用于计算立体匹配&#xff08;Stereo Matching&#xff09;的…

远程帮客户解决“应用程序无法正常启动0xc000007b,请单击确定关闭应用程序”的问题

今天收到反馈&#xff0c;SmartPipe软件&#xff0c;在客户机器上报错&#xff0c;无法正常运行&#xff0c;采用远程控制软件进入客户电脑&#xff0c;发现电脑报错如下&#xff1a; 因为客户的电脑是win7&#xff0c;而之前发生过win7电脑上无法运行OCC编写的软件的情况&…

产品经理-一份标准需求文档的8个模块(14)

一份标准优秀的产品需求文档包括&#xff1a; ❑ 封面&#xff1b; ❑ 文档修订记录表&#xff1b; ❑ 目录&#xff1b; ❑ 引言&#xff1b; ❑ 产品概述&#xff1a;产品结构图 ❑ 详细需求说明&#xff1a;产品逻辑图、功能与特性简述列表、交互/视觉设计、需求详细描述&am…

Java实现数据结构——双链表

目录 一、前言 二、实现 2.1 类的创建 三、对链表操作实现 3.1 打印链表 3.2 插入数据 3.2.1 申请新节点 3.2.2 头插 ​编辑 3.2.3 尾插 3.2.4 链表长度 3.2.5 任意位置插入 3.3 删除数据 3.3.1 头删 3.3.2 尾删 3.3.3 删除指定位置数据 3.3.4 删除指定数据 3…

涉案财物管理系统|八大模块可视化展示

涉案财物管理系统DW-S405系统基于物联网技术规范涉案财物管理流程&#xff0c;确保涉案财物的安全性、完整性和合法性&#xff1b;可以提高办案效率&#xff0c;减少办案成本&#xff0c;实现资源共享。 涉案财物管理系统DW-S405主要分为 8 大模块数据展示。 1、案件信息&…

Linux C | 管道open打开方式

Linux C | 管道open打开方式 1.参考 1. 管道 2.现象 是的&#xff0c;这段代码在调用 open(AUDIOIN_FIFO, O_RDONLY) 时可能会被阻塞。原因是 FIFO&#xff08;命名管道&#xff09;在以只读模式打开时&#xff0c;如果没有其他进程以写模式打开该 FIFO&#xff0c;open 调用将…

防火墙综合实验二

目录 实验要求 IP地址配置 需求七 需求八 需求九 需求十 需求十一 实验要求 接防火墙综合实验一&#xff01; 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换)。 8&#xff0c;分公司设备可以通过…

美无定论,娜扎亦菲各自绽放你更爱哪一款?

娜扎亦菲各自绽放你更爱哪一款&#xff1f; 哎呀&#xff0c;这个问题可真是让我头疼呢&#xff0c; 就像让我在两个糖果店里选择最甜的那一颗一样难&#xff01; 古力娜扎和刘亦菲&#xff0c;两位都是娱乐圈里璀璨的明珠&#xff0c; 美得各有千秋&#xff0c;让人怎么舍得…

C++基础入门(上)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 C基础入门(上) 收录于专栏【C语法基础】 本专栏旨在分享学习C的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. C发展历史 2. C版本…

Goland 通道

channel通道 目录 channel通道 channel介绍 channel基本使用 有缓存通道和无缓存通道的区别 通道的初始化&#xff0c;写入数据到通道&#xff0c;从通道读取数据及基本的注意事项 channel的关闭和遍历 channel的关闭 为什么关闭 如何优雅地关闭通道 channel的遍历 chan…