前言
操作日志几乎存在于每个系统中。操作日志和系统日志不一样,操作日志必须要做到简单易懂。
操作日志的使用场景
操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。
操作日志的记录格式大概分为下面几种: * 单纯的文字记录,比如:2021-09-1610:00 订单创建。 * 简单的动态的文本记录,比如:2021-09-16 10:00 订单创建,订单 :NO.11089999,其中涉及变量订单 “NO.11089999”。 * 修改类型的文本,包含修改前和修改后的值,比如:2021-09-16 10:00 用户小明修改了订单的配送地址:从“金灿灿小区”修改到“银盏盏小区” ,其中涉及变量配送的原地址“金灿灿小区”和新地址“银盏盏小区”。 * 修改表单,一次会修改多个字段。
实现方式
使用Canal监听数据库记录操作日志
Canal 是一款基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费的开源组件,通过采用监听数据库 Binlog 的方式,这样可以从底层知道是哪些数据做了修改,然后根据更改的数据记录操作日志。
这种方式的优点是和业务逻辑完全分离。缺点也很明显,局限性太高,只能针对数据库的更改做操作日志记录,如果修改涉及到其他团队的 RPC 的调用,就没办法监听。
通过日志文件的方式记录
log.info(” 订单创建 “)
log.info(” 订单已经创建,订单编 :{}“, orderNo)
log.info(” 修改了订单的配送地址:从 “{}” 修改到 “{}”, ” 金灿灿小区 “, ” 银盏盏小区 “)
这种方式的操作记录需要解决三个问题:
问题一:操作人如何记录
借助 SLF4J 中的 MDC 工具类,把操作人放在日志中,然后在日志中统一打印出来。首先在用户的拦截器中把用户的标识 Put 到 MDC 中(MDC.put(“userId”, userNo);)。
其次,把 userId 格式化到日志中,使用 %X{userId} 可以取到 MDC 中用户标识。
” %d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5level %X{userId}%logger{30}.%method:%L – %msg%n”
问题二:操作日志如何和系统日志区分开
通过配置 Log 的配置文件,把有关操作日志的 Log 单独放到一日志文件中。然后在Java代码单独的记录业务日志。
问题三:如何生成可读懂的日志文案
可以采用 LogUtil 的方式,也可以采用切面的方式生成日志模板,后续内容将会进行介绍。这样就可以把日志单独保存在一个文件中,然后通过日志收集可以把日志保存在 Elasticsearch 或者数据库中,接下来看下如何生成可读的操作日志。
通过LogUtil的方式记录日志
LogUtil.log(orderNo, ” 订单创建 “, ” 小明 “) 模板
LogUtil.log(orderNo, ” 订单创建,订单 “+“NO.11089999”, ” 小明 “)
String template = ” 用户 %s 修改了订单的配送地址:从 “%s” 修改到 “%s””
LogUtil.log(orderNo, String.format(tempalte, ” 小明 “, ” 金灿灿小区 “, ” 银盏盏小区 “), ” 小明 “)
这里解释下为什么记录操作日志的时候都绑定了一个 OrderNo,因为操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。当查询业务的操作日志的时候,会查询针对这个订单的的所有操作,所以代码中加上了OrderNo,记录操作日志的时候需要记录下操作人,所以传了操作人“小明”进来.
当业务变得复杂后,记录操作日志放在业务代码中会导致业务的逻辑比较繁杂,最后导致 LogUtils.logRecord() 方法的调用存在于很多业务的代码中,而且类似 getLogContent() 这样的方法也散落在各个业务类中,对于代码的可读性和可维护性来说是一个灾难。
方法注解实现操作日志
为了解决上面问题,一般采用 AOP 的方式记录日志,让操作日志和业务逻辑解耦,接下来看一个简单的 AOP 日志的例子。
AOP生成动态的操作日志
动态模板
operator参数非必填。
LogRecordContext 解决了操作日志模板上使用方法参数以外变量的问题,同时避免了为了记录操作日志修改方法签名的设计.
自定义函数。如果我们可以通过自定义函数把用户 ID 转换为用户姓名和电话,那么就能解决这一问题,按照这个思路,我们把模板修改为下面的形式:
其中deliveryUser是自定义函数,使用大括 把Spring的SpEL表达式包裹起来。
进一步优化,净化业务代码:
通过直接增加一个自定义函数queryOldUser,查到之前的配送人员。
代码实现解析
代码结构

模块介绍
AOP拦截逻辑
针对@LogRecord注解分析出需要记录的操作日志,然后操作日志持久化。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!