背景
工作中开发项目时,审计功能模块要求服务在动作时记录一些日志,供审计模块汇总分析,形成审计服务。为了符合,各个服务被要求记录下了很多和服务流程无关的冗余信息。主要包括在动作触发时刻实体的部分属性和关联实体的属性信息。避免属性或关联变动后,历史状态丢失。
但是这样的方式导致很多操作原本仅限于一个实体的某个属性,由于为了留存更多的的记录,需要查询更多的信息来写入日志,会需要消耗更多的数据库查询资源。另外如果需求的属性没有被预见到,那已经被记录的日志中未记录的属性信息都会丢失,后续没有办法弥补。
思考
理论上日志中仅应该记录不可变信息,比如主体 ID、操作名称、操作属性。而当时主体的其他属性、其他关联的主体应该通过数据记录查询出来。另外如果需要兼顾查询性能,可以对查询做缓存。
这样可以使查询更加有弹性,日志记录侵入服务逻辑。也可以更好地适应不同的查询场景。
以审计修改密码行为为例,如果我们希望在审计时同时显示当时账号的用户名。对某条记录而言,应该以该条记录的时间向前查询,通过记录中存储的账号 ID 找到最新的一条对用户名的修改(可能是修改操作,也可能是创建操作中指定了用户名)。然后基于那次修改记录,显示用户名。
但是上述推导过程是有性能开销的,如果每次查询的时候都需要做这样的操作,会造成性能损失。最直接的方式是在每次查询时,对以查询过的信息条目做缓存,每次只有未被查询过的记录需要重新推导出完整信息条目。另一种方式是实现一个触发器,在新的日志被写入时,立即生成完整的信息条目。
审计问题
按照上述方案,可能会认为审计模块的开发需要了解业务细节。首先审计结果和业务逻辑必然是强关联的,这一点不可避免。但是是否意味着需要审计开发人员去了解复杂的业务逻辑的?但其实并非如此。我们应该将日志汇总成数据条目的任务拆分出来,由运营人员通过 ETL^1 等工具进行汇总,向外提供接口或信息条目记录。而审计模块需要提供能力接收和解析这些记录,通过模块功能将记录可视化。