**前言:**我们项目中可能有这种需求,每个人请求了哪些接口?做了什么事情?参数是什么?重要的接口我们需要记录操作日志以便查找。操作日志和系统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?我们不可能在每个接口中去一一处理,可以借助Spring提供的AOP能力+自定义注解轻松应对。
一、操作日志简介
日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch中;如果存在数据库中,分表是不错的选择。
1.1、系统日志和操作日志的区别
系统日志:系统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志。
操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。
操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。
比如LogUtil.log(orderNo,“订单创建”,“小明”) :这里解释下为什么记录操作日志的时候都绑定了一个 OrderNo,因为操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。当查询业务的操作日志的时候,会查询针对这个订单的的所有操作,所以代码中加上了 OrderNo,记录操作日志的时候需要记录下操作人,所以传了操作人“小明”进来。
操作日志的记录格式大概分为下面几种:
-
单纯的文字记录,比如:2021-09-16 10:00 订单创建。
-
简单的动态的文本记录,比如:2021-09-16 10:00 订单创建,订单号:NO.11089999,其中涉及变量订单号“NO.11089999”。
-
修改类型的文本,包含修改前和修改后的值,比如:2021-09-16 10:00 用户小明修改了订单的配送地址:从“金灿灿小区”修改到“银盏盏小区” ,其中涉及变量配送的原地址“金灿灿小区”和新地址“银盏盏小区”。
-
修改表单,一次会修改多个字段。
1.2、操作日志记录实现方式
(1)使用 Canal 监听数据库记录操作日志
Canal是阿里开源的一款基于 MySQL 数据库增量日志解析中间件,提供增量数据订阅和消费的开源组件,通过采用监听数据库 Binlog 的方式,这样可以从底层知道是哪些数据做了修改,然后根据更改的数据记录操作日志。
这种方式的优点是和业务逻辑完全分离。缺点也很明显,局限性太高,只能针对数据库的更改做操作日志记录,如果修改涉及到其他团队的 RPC 的调用,就没办法监听数据库了。举个例子:给用户发送通知,通知服务一般都是公司内部的公共组件,这时候只能在调用 RPC 的时候手工记录发送通知的操作日志了。
(2)高度代码耦合:在业务逻辑中直接调用日志记录接口
通过日志文件的方式记录,这样就可以把日志单独保存在一个文件中,然后通过日志收集可以把日志保存在 Elasticsearch或者数据库中,接下来我们看下如何生成可读的操作日志。
//操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。
log.info("订单创建")
log.info("订单已经创建,订单编号:{}",?orderNo)
log.info("修改了订单的配送地址:从“{}”修改到“{}”,?"金灿灿小区",?"银盏盏小区")
但是当业务变得复杂后,记录操作日志放在业务代码中会导致业务的逻辑比较繁杂。
(3)采用AOP方式:AOP方式能和业务逻辑解耦
为了解决上面问题,一般采用 AOP 的方式记录日志,让操作日志和业务逻辑解耦,接下来看一个简单的 AOP 日志的例子。
@LogRecord(content="修改了配送地址")
public?void?modifyAddress(updateDeliveryRequest?request){
????//?更新派送信息?电话,收件人、地址
????doUpdate(request);
}
我们可以在注解的操作日志上记录固定文案,这样业务逻辑和业务代码可以做到解耦,让我们的业务代码变得纯净起来。可能有同学注意到,上面的方式虽然解耦了操作日志的代码,但是记录的文案并不符合我们的预期,文案是静态的,没有包含动态的文案,因为我们需要记录的操作日志是:用户%s修改了订单的配送地址,从“%s”修改到“%s”。接下来,我们介绍一下如何优雅地使用 AOP 生成动态的操作日志。
二、AOP面向切面编程
2.1、AOP简介
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。**这种在运行时,动态地将代码切入到类的指定方法或指定位置上的编程思想就是面向切面的编程。**利用AOP可以将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来作为公共部分,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.2、AOP作用
日志记录,性能统计,安全控制,事务处理,异常处理等等。
在面向切面编程AOP的思想里面,核心业务和切面通用功能(例如事务处理、日志管理、权限控制等)分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。这种思想有利于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
2.3、AOP相关术语
通知(Advice)
通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。
-
前置通知(Before):在目标方法调用前调用通知功能;
-
后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
-
返回通知(AfterReturning):在目标方法成功执行之后