⼀个注解,搞定SpringBoot操作⽇志此组件解决的问题是:「谁」在「什么时间」对「什么」做了「什么事」
基于此,本⽂给⼤家推荐⼀个⼯具,该⼯具组件⼗分强⼤!
本组件⽬前针对 Spring-boot 做了 Autoconfig,如果是 SpringMVC,也可⾃⼰在 xml 初始化 bean
使⽤⽅式
基本使⽤
maven依赖添加SDK依赖
<dependency>
<groupId>uzt</groupId>
<artifactId>bizlog-sdk</artifactId>
<version>1.0.1</version>
</dependency>
SpringBoot⼊⼝打开开关,添加 @EnableLogRecord 注解
tenant是代表租户的标识,⼀般⼀个服务或者⼀个业务下的多个服务都写死⼀个 tenant 就可以
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableTransactionManagement
@EnableLogRecord(tenant = "st")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
⽇志埋点
1. 普通的记录⽇志 pefix:是拼接在 bizNo 上作为 log 的⼀个标识。避免 bizNo 都为整数 ID 的时候和其他的业务中的 ID 重复。⽐如
订单 ID、⽤户 ID 等 bizNo:就是业务的 ID,⽐如订单ID,我们查询的时候可以根据 bizNo 查询和它相关的操作⽇志 success:⽅法调⽤成功后把 success 记录在⽇志的内容中 SpEL 表达式:其中⽤双⼤括号包围起来的(例如:
{{#order.purchaseName}})#order.purchaseName 是 SpEL表达式。Spring中⽀持的它都⽀持的。⽐如调⽤静态⽅法,三⽬表达式。SpEL 可以使⽤⽅法中的任何参数
@LogRecordAnnotation(success = "{{#order.purchaseName}}下了⼀个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}")
public boolean createOrder(Order order) {
log.info("【创建订单】orderNo={}", OrderNo());
// db insert order
return true;
}
此时会打印操作⽇志 “张三下了⼀个订单,购买商品「超值优惠红烧⾁套餐」,下单结果:true”
2. 期望记录失败的⽇志, 如果抛出异常则记录fail的⽇志,没有抛出记录 success 的⽇志
@LogRecordAnnotation(
fail = "创建订单失败,失败原因:「{{#_errorMsg}}」",
success = "{{#order.purchaseName}}下了⼀个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}")
public boolean createOrder(Order order) {
log.info("【创建订单】orderNo={}", OrderNo());
// db insert order
return true;
}
其中的 #_errorMsg 是取的⽅法抛出异常后的异常的 errorMessage。
1. ⽇志⽀持种类 ⽐如⼀个订单的操作⽇志,有些操作⽇志是⽤户⾃⼰操作的,有些操作是系统运营⼈员做了修改产⽣的操作⽇志,我们
系统不希望把运营的操作⽇志暴露给⽤户看到, 但是运营期望可以看到⽤户的⽇志以及运营⾃⼰操作的⽇志,这些操作⽇志的bizNo 都是订单号,所以为了扩展添加了类型字段,主要是为了对⽇志做分类,查询⽅便,⽀持更多的业务。
@LogRecordAnnotation(
fail = "创建订单失败,失败原因:「{{#_errorMsg}}」",
category = "MANAGER",
success = "{{#order.purchaseName}}下了⼀个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}")
public boolean createOrder(Order order) {
log.info("【创建订单】orderNo={}", OrderNo());
// db insert order
return true;
}
1. ⽀持记录操作的详情或者额外信息 如果⼀个操作修改了很多字段,但是success的⽇志模版⾥⾯防⽌过长不能把修改详情全部展⽰出
来,这时候需要把修改的详情保存到 detail 字段, detail 是⼀个 String ,需要⾃⼰序列化。这⾥的 #String() 是调⽤了Order 的 toString() ⽅法。如果保存 JSON,⾃⼰重写⼀下 Order 的 toString() ⽅法就可以。
@LogRecordAnnotation(
fail = "创建订单失败,失败原因:「{{#_errorMsg}}」",
category = "MANAGER_VIEW",
detail = "{{#String()}}",
success = "{{#order.purchaseName}}下了⼀个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}")
error parse newpublic boolean createOrder(Order order) {
log.info("【创建订单】orderNo={}", OrderNo());
// db insert order
return true;
}
1. 如何指定操作⽇志的操作⼈是什么?框架提供了两种⽅法 第⼀种:⼿⼯在LogRecord的注解上指定。这种需要⽅法参数上有
operator
@LogRecordAnnotation(
fail = "创建订单失败,失败原因:「{{#_errorMsg}}」",
category = "MANAGER_VIEW",
detail = "{{#String()}}",
operator = "{{#currentUser}}",
success = "{{#order.purchaseName}}下了⼀个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}")
public boolean createOrder(Order order, String currentUser) {
log.info("【创建订单】orderNo={}", OrderNo());
// db insert order
return true;
}
这种⽅法⼿⼯指定,需要⽅法参数上有 operator 参数,或者通过 SpEL 调⽤静态⽅法获取当前⽤户。
第⼆种:通过默认实现类来⾃动的获取操作⼈,由于在⼤部分web应⽤中当前的⽤户都是保存在⼀个线程上下⽂中的,所以每个注解都加⼀个operator获取操作⼈显得有些重复劳动,所以提供了⼀个扩展接⼝来获取操作⼈ 框架提供了⼀个扩展接⼝,使⽤框架的业务可以implements 这个接⼝⾃⼰实现获取当前⽤户的逻辑, 对于使⽤ Springboot 的只需要实现 IOperatorGetService 接⼝,然后把这个Service 作为⼀个单例放到 Spring 的上下⽂中。使⽤ Spring Mvc 的就需要⾃⼰⼿⼯装配这些 bean 了。
@Configuration
public class LogRecordConfiguration {
@Bean
public IOperatorGetService operatorGetService() {
return () -> Optional.CurrentUser())
.map(a -> new MisId()))
.orElseThrow(() -> new IllegalArgumentException("user is null"));
}
}
//也可以这么搞:
@Service
public class DefaultOperatorGetServiceImpl implements IOperatorGetService {
@Override
public OperatorDO getUser() {
OperatorDO operatorDO = new OperatorDO();
operatorDO.setOperatorId("SYSTEM");
return operatorDO;
}
}
1. ⽇志⽂案调整 对于更新等⽅法,⽅法的参数上⼤部分都是订单ID、或者产品ID等, ⽐如下⾯的例⼦:⽇志记录的success内容
是:“更新了订单{{#orderId}},更新内容为…”,这种对于运营或者产品来说难以理解,所以引⼊了⾃定义函数的功能。使⽤⽅法是在原来的变量的两个⼤括号之间加⼀个函数名称 例如 “{ORDER{#orderId}}” 其中 ORDER 是⼀个函数名称。只有⼀个函数名称是不够的,需要添加这个函数的定义和实现。可以看下⾯例⼦ ⾃定义的函数需要实现框架⾥⾯的IParseFunction的接⼝,需要实现两个⽅法:
functionName() ⽅法就返回注解上⾯的函数名;
apply()函数参数是 "{ORDER{#orderId}}"中SpEL解析的#orderId的值,这⾥是⼀个数字1223110,接下来只需要在实现的类中把 ID 转换为可读懂的字符串就可以了, ⼀般为了⽅便排查问题需要把名称和ID都展⽰出来,例如:"订单名称(ID)"的形式。
这⾥有个问题:加了⾃定义函数后,框架怎么能调⽤到呢?答:对于Spring boot应⽤很简单,只需要把它暴露在Spring的上下⽂中就可以了,可以加上Spring的 @Component 或者 @Service 很⽅便。Spring mvc 应⽤需要⾃⼰装配 Bean。
// 没有使⽤⾃定义函数
@LogRecordAnnotation(success = "更新了订单{{#orderId}},更新内容为....",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}",
detail = "{{#String()}}")
public boolean update(Long orderId, Order order) {
return false;
}
//使⽤了⾃定义函数,主要是在 {{#orderId}} 的⼤括号中间加了 functionName
@LogRecordAnnotation(success = "更新了订单ORDER{#orderId}},更新内容为...",
prefix = LogRecordType.ORDER, bizNo = "{{#derNo}}",
detail = "{{#String()}}")
public boolean update(Long orderId, Order order) {
return false;
}
// 还需要加上函数的实现
@Component
public class OrderParseFunction implements IParseFunction {
@Resource
@Lazy //为了避免类加载顺序的问题最好为Lazy,没有问题也可以不加
private OrderQueryService orderQueryService;
@Override
public String functionName() {
// 函数名称为 ORDER
return "ORDER";
}
@Override
//这⾥的 value 可以吧 Order 的JSON对象的传递过来,然后反解析拼接⼀个定制的操作⽇志内容
public String apply(String value) {
if(StringUtils.isEmpty(value)){
return value;
}
Order order = orderQueryService.queryOrder(Long.parseLong(value));
//把订单产品名称加上便于理解,加上 ID 便于查问题
ProductName().concat("(").concat(value).concat(")");
}
}
1. ⽇志⽂案调整 使⽤ SpEL 三⽬表达式
@LogRecordAnnotation(prefix = LogRecordTypeConstant.CUSTOM_ATTRIBUTE, bizNo = "{{#businessLineId}}", success = "{{#disable ? '停⽤' : '启⽤'}}了⾃定义属性{ATTRIBUTE{#attributeId}}")
public CustomAttributeVO disableAttribute(Long businessLineId, Long attributeId, boolean disable) {
return xxx;
}
框架的扩展点
重写OperatorGetServiceImpl通过上下⽂获取⽤户的扩展,例⼦如下
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论