一个小小的注解,帮你搞定 SpringBoot 操作日志

maven 依赖添加 SDK 依赖

=================

io.github.mouzt

bizlog-sdk

1.0.4

SpringBoot 入口打开开关, 添加 @EnableLogRecord 注解

=========================================

tenant 是代表租户的标识,一般一个服务或者一个业务下的多个服务都写死一个 tenant 就可以

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

@EnableTransactionManagement

@EnableLogRecord(tenant = “com.mzt.test”)

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 = “{{#order.orderNo}}”)

public boolean createOrder(Order order) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// db insert order

return true;

}

此时会打印操作日志 “张三下了一个订单, 购买商品「超值优惠红烧肉套餐」, 下单结果: true”

2. 期望记录失败的日志, 如果抛出异常则记录 fail 的日志,没有抛出记录 success 的日志

====================================================

@LogRecordAnnotation(

fail = “创建订单失败,失败原因:「{{#_errorMsg}}」”,

success = “{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”)

public boolean createOrder(Order order) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// db insert order

return true;

}

其中的 #_errorMsg 是取的方法抛出异常后的异常的 errorMessage。

3. 日志支持种类

==========

比如一个订单的操作日志,有些操作日志是用户自己操作的,有些操作是系统运营人员做了修改产生的操作日志,我们系统不希望把运营的操作日志暴露给用户看到,

@LogRecordAnnotation(

fail = “创建订单失败,失败原因:「{{#_errorMsg}}」”,

category = “MANAGER”,

success = “{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”)

public boolean createOrder(Order order) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// db insert order

return true;

}

4. 支持记录操作的详情或者额外信息

===================

如果一个操作修改了很多字段,但是 success 的日志模版里面防止过长不能把修改详情全部展示出来,这时候需要把修改的详情保存到 detail 字段,

detail 是一个 String ,需要自己序列化。这里的 #order.toString() 是调用了 Order 的 toString() 方法。

如果保存 JSON,自己重写一下 Order 的 toString() 方法就可以。

@LogRecordAnnotation(

fail = “创建订单失败,失败原因:「{{#_errorMsg}}」”,

category = “MANAGER_VIEW”,

detail = “{{#order.toString()}}”,

success = “{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”)

public boolean createOrder(Order order) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// db insert order

return true;

}

5. 如何指定操作日志的操作人是什么?框架提供了两种方法

=============================

  • 第一种:手工在 LogRecord 的注解上指定。这种需要方法参数上有 operator

@LogRecordAnnotation(

fail = “创建订单失败,失败原因:「{{#_errorMsg}}」”,

category = “MANAGER_VIEW”,

detail = “{{#order.toString()}}”,

operator = “{{#currentUser}}”,

success = “{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,下单结果:{{#_ret}}”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”)

public boolean createOrder(Order order, String currentUser) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// 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.of(

OrgUserUtils.getCurrentUser())

.map(a -> new OperatorDO(a.getMisId()))

.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;

}

}

6. 日志文案调整

==========

对于更新等方法,方法的参数上大部分都是订单 ID、或者产品 ID 等,

比如下面的例子:日志记录的 success 内容是:“更新了订单 {{#orderId}}, 更新内容为…”,这种对于运营或者产品来说难以理解,所以引入了自定义函数的功能。

使用方法是在原来的变量的两个大括号之间加一个函数名称 例如 “{ORDER{#orderId}}” 其中 ORDER 是一个函数名称。只有一个函数名称是不够的, 需要添加这个函数的定义和实现。可以看下面例子

自定义的函数需要实现框架里面的 IParseFunction 的接口,需要实现两个方法:

  • functionName() 方法就返回注解上面的函数名;

  • apply() 函数参数是 “{ORDER{#orderId}}” 中 SpEL 解析的 #orderId 的值,这里是一个数字 1223110,接下来只需要在实现的类中把 ID 转换为可读懂的字符串就可以了,

这里有个问题:加了自定义函数后,框架怎么能调用到呢?

// 没有使用自定义函数

@LogRecordAnnotation(success = “更新了订单{{#orderId}},更新内容为…”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”,

detail = “{{#order.toString()}}”)

public boolean update(Long orderId, Order order) {

return false;

}

//使用了自定义函数,主要是在 {{#orderId}} 的大括号中间加了 functionName

@LogRecordAnnotation(success = “更新了订单ORDER{#orderId}},更新内容为…”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”,

detail = “{{#order.toString()}}”)

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 便于查问题

return order.getProductName().concat(“(”).concat(value).concat(“)”);

}

}

7. 日志文案调整 使用 SpEL 三目表达式

========================

@LogRecordAnnotation(prefix =

LogRecordTypeConstant.CUSTOM_ATTRIBUTE, bizNo = “{{#businessLineId}}”,

success = “{{#disable ? ‘停用’ : ‘启用’}}了自定义属性{ATTRIBUTE{#attributeId}}”)

public CustomAttributeVO disableAttribute(Long businessLineId, Long attributeId, boolean disable) {

return xxx;

}

8. 日志文案调整 模版中使用方法参数之外的变量

=========================

可以在方法中通过

LogRecordContext.putVariable(variableName, Object) 的方法添加变量,第一个对象为变量名称,后面为变量的对象,

@Override

@LogRecordAnnotation(

success = “{{#order.purchaseName}}下了一个订单,购买商品「{{#order.productName}}」,测试变量「{{#innerOrder.productName}}」,下单结果:{{#_ret}}”,

prefix = LogRecordType.ORDER, bizNo = “{{#order.orderNo}}”)

public boolean createOrder(Order order) {

log.info(“【创建订单】orderNo={}”, order.getOrderNo());

// db insert order

Order order1 = new Order();

order1.setProductName(“内部变量测试”);

LogRecordContext.putVariable(“innerOrder”, order1);

return true;

}

9. 函数中使用 LogRecordContext 的变量

==============================

使用

LogRecordContext.putVariable(variableName, Object) 添加的变量除了可以在注解的 SpEL 表达式上使用,还可以在自定义函数中使用, 这种方式比较复杂,下面例子中示意了列表的变化,比如从 [A,B,C] 改到 [B,D] 那么日志显示:「删除了 A,增加了 D」

@LogRecord(success = “{DIFF_LIST{‘文档地址’}}”, bizNo = “{{#id}}”, prefix = REQUIREMENT)

public void updateRequirementDocLink(String currentMisId, Long id, List docLinks) {

RequirementDO requirementDO = getRequirementDOById(id);

LogRecordContext.putVariable(“oldList”, requirementDO.getDocLinks());

LogRecordContext.putVariable(“newList”, docLinks);

requirementModule.updateById(“docLinks”, RequirementUpdateDO.builder()

.id(id)

.docLinks(docLinks)

.updater(currentMisId)

.updateTime(new Date())

.build());

}

@Component

public class DiffListParseFunction implements IParseFunction {

@Override

public String functionName() {

return “DIFF_LIST”;

}

@SuppressWarnings(“unchecked”)

@Override

public String apply(String value) {

if (StringUtils.isBlank(value)) {

return value;

}

List oldList = (List)

LogRecordContext.getVariable(“oldList”);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-pHPjgk6w-1715180944155)]

[外链图片转存中…(img-bU8b5qcN-1715180944156)]

[外链图片转存中…(img-Y5G0kWti-1715180944157)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值