需求分析
由于公司的需求,需要建立一套独立于业务系统的业务操作日志记录功能,而且要求在接入使用中尽量简单方便。举个例子,目前整个系统有多个业务微服务,如用户信息服务、订单服务、商品服务等等。当我们想记录每个服务的每个具体业务操作时,就得有对应的业务操作日志表,然后在那个功能接口专门写程序记录到业务操作日志表里。(需要注意的是,本篇博文所说的业务操作日志是指的具体某个业务操作的历史记录,需要包含业务操作内容,而不是指功能接口请求日志,两者是不一样的。功能接口的请求日志只需要在各个服务的请求过滤器里插入请求日志记录就行,它可以记录到用户请求了某个接口来体现用户操作了哪个功能,但是没办法体现用户操作这个接口做了什么业务操作。)
以下举例几个需要业务操作日志的场景:
- 用户修改了个人信息,需要提供修改的日志记录,比如当前用户在什么时间修改了什么内容,修成成功了还是失败了。
- 管理员后台操作,比如更新了某个订单记录或者删除了某件商品信息,需要记录对应的操作日志:更新了订单的什么信息,原数据是啥,修改后的新数据是啥;删除了哪件商品的信息,商品名称是啥…
类似的需要业务操作日志的场景还有很多,基本上每个成熟的系统都少不了业务操作日志的记录,因为它可以帮助我们对用户操作进行归档和对用户的行为进行更细致的分析,同样也能给用户提供更好的业务操作服务体验。
实现方式
实现业务操作日志的方式有多种,下面就简单介绍一下比较传统的各个功能各自实现的方式和本篇博文主要实现的通用日志的方式。
1. 传统方式
传统的实现方式是单独为每个功能设计对应的业务操作记录功能,比如针对用户信息的修改,单独设计一个“用户修改信息记录”的数据表,专门用来保存用户修改个人信息的历史记录的。其实这也是目前很多系统常用的方式,针对不同功能提供不同的历史记录表(也可以叫日志表)。这种方式需要每个功能都有属于自己的操作日志表,而且在代码层面上,每个业务操作都得手动写代码去维护这张日志表,而且后续功能越来越多,需要的专属操作日志表也越来越多,不利于后续的业务日志分析操作。
2. 通用方式
通用的方式是将所有的业务操作的日志都记录到一个通用的“业务操作日志表”里面。这样就可以不用单独为每个业务功能都设计一张专属的业务日志表,而且在代码层面上也将更加灵活,可以直接调用写入日志的方法直接将操作内容写入该通用的“业务操作日志表”里面,不用针对不通的功能去维护不同的写入日志代码。这也对后续的业务日志分析功能更加友好,因为我只需要去分析一张表就可以了。
功能架构
本次通用操作日志功能,我们可以分为两个部分:一个是由具体业务系统引入的业务日志记录模块,另一个是独立于业务系统的业务日志处理分析模块。
- 业务日志收集模块
该模块将会作为jar的形式由各个业务微服务来引入,主要实现采集业务操作并通过消息队列对外进行业务操作日志的推送。 - 日志接收和分析模块
该模块会单独设计成一个独立于业务模块以外的专属于日志分析的微服务,主要用来接收各个业务微服务推送过来的业务操作日志,并且对业务日志进行分析和将日志内容提供给其他业务服务或者直接提供给用户。
开发实现
业务日志收集模块
我们先来进行第一个功能模块的开发,那就是“业务日志收集模块”。业务日志收集模块的主要功能是收集业务操作,这意味着该模块需要与具体的业务服务相结合,所以我们将其设计为一个可以让各个业务模块直接引入的jar包的形式。这样每个业务微服务可以直接通过maven或本地引入等方式加载日志收集模块jar。
在进行具体开发之前,我们先提出两个问题,第一个是如何采集业务操作?第二个问题是采集后该如何推送?
- 如何采集业务日志:
用户的每一个业务操作其实可以看做程序的每一个函数的执行,比如用户修改个人信息,那他自然是调用了修改信息的程序函数。所以我们在收集业务操作时,应该针对方法的执行来收集。说白了就是对函数进行监听(增强),只要调用了业务函数,那就记录下对应的日志。使用spring的我们很容易就想到了AOP功能,我们可以使用切面功能来对方法进行增强,这样就可以在方法执行前后做我们的日志记录操作了。 - 如何推送业务日志:
第一步已经采集到了业务操作的日志,但是我们需要把这些日志信息推送到我们的第二个模块“日志接收和分析模块”。这样就由第二个模块对日志进行加工处理(存到数据库或者进行分析后提供给用户等等)。目前我使用的方式是通过一些消息组件进行日志消息的推送,我使用的是kafka。
明白了以上两个问题和解决方案之后,我们就可以正式开始进入业务日志收集模块的开发。
首先新建一个项目(业务日志收集模块),我这里项目名称叫operate-log
因为收集日志需要使用AOP切面的方式,所以我们应该想一个切点,也就是什么情况下我会去记录某一个业务操作作为日志呢?还是以用户修改个人信息这个操作来举例,以下就是修改用户信息的接口方法
要想对这个方法进行切入,那就得将这个方法设为切点,而业务操作的方法又不止这一个修改用户信息,也会有别的比如下订单、删除商品等操作,所以我们可以创建一个自定义的注解,用来标记某个业务操作函数作为我们的切点(也就是作为收集日志的方法)。接下来我们创建@OperateLog注解,用它来标识我们的修改用户信息函数。
这个注解不仅用来标识我们这个函数是否是需要收集日志的函数,还能包含我们对本次日志记录的一些说明描述,其实它就代表了我们整个操作日志的内容。我们来解析一下这个注解的每个属性:
(1)日志类型:表示记录的日志的类型,共有用户业务操作、接口操作、任务操作三种类型、默认是用户业务操作。
(2)操作类型:有读取、新增、更新、删除、计算、审核、导入、导出、进入、退出、其他操作。默认为读取操作
1 |
|
(3)操作描述:该操作的业务描述
(4)操作内容:具体的操作内容和更新内容
(5)业务流程:当前日志所属的业务,用户自定约束规则
(6)操作人:当前操作用户ID
(7)操作人名称:操作人的名称
(8)成功表达式:当前操作的成功状态的表达式,用来判断当前操作的状态是否成功,表达式的返回值需要为boolean类型
(9)扩展1和2:用户自己用来和业务系统进行绑定的字段,例如该日志所对应的业务表信息或者具体某个业务操作的关联对应等等。
创建好注解之后,我们就可以在我们需要记录日志的函数上面使用我们的注解。比如针对修改个人信息的函数
该注解的operateType表示这是个UPDATE操作(由我们自定义的枚举来决定,我们可以添加枚举值,比如删除操作,查询操作,导入操作,导出操作等等),desc表示当前日志的描述,detail一般用来记录业务日志的具体操作内容,process用来表示当前日志所属的业务流程是什么,比如这次的日志是属于用户信息变更流程(方便我们后续查询,比如当我们要查询用户信息变更的日志记录时,我们就可以通过process=updateUserInfo来获取专属于用户信息变更的日志记录,而不是导出订单或删除商品等别的业务的日志)。
本章结尾
本篇主要讲通用日志系统的需求来源和设计思路,让大家对为什么要设计一个通用的日志服务有一个大概的了解。同时本篇的开发实现内容主要是创建了一个OperateLog注解,该注解是“业务日志收集模块”的核心,我们后续的日志内容主要就由解析该注解而得来。所以充分理解OperateLog注解对我们后续的开发很有帮助。