乐趣区

关于github:如何使用注解优雅的记录操作日志-萌新写开源-01

写在结尾

本文探讨如何优雅的记录操作日志,并且 实现了一个 SpringBoot Starter(取名 log-record-starter),不便的应用注解记录操作日志,并将日志数据推送到指定数据管道(音讯队列等)

本文灵感来源于美团技术团队的文章:如何优雅地记录操作日志?。文中应用的局部定义形容和示例来源于美团原文,请知悉。

本文作为《萌新写开源》的开篇,先把我的项目成品介绍给大家,之后的文章会具体介绍,如何一步步将集体我的项目做成一个大家都能参加的开源我的项目(如何写 SpringBoot Starter,如何上传到 Maven 仓库,如何设计和应用注解和切面等),麻烦大家多多点赞反对,这是我更新的能源。请大家释怀,公众号还会继续更新,我没有忘掉明码。:)——蛮三刀酱

本文目录:

  • 什么是操作日志?
  • Java 中常见的操作日志实现形式
  • 实战:通过注解实现操作日志的记录

什么是操作日志?

定义:操作日志 次要是指对某个对象进行新增操作或者批改操作后记录下这个新增或者批改,操作日志要求可读性比拟强,因为它次要是给用户看的,比方订单的物流信息,用户须要晓得在什么工夫产生了什么事件。再比方,客服对工单的解决记录信息。

以咱们零碎外部应用的一个 CRM 零碎举例,外面每个联系人的材料都会有操作历史:

这些数据就是操作系统日志,这些数据通常会以结构化数据的模式存储在数据库中,对于开发来说,这种日志的代码逻辑通常是十分法则,比方读取变动前和变动后的数据,获取以后操作人和操作工夫等等。

常见的操作日志实现形式

在小型我的项目中,这种日志记录的操作通常会以提供一个接口或整个日志记录 Service 来实现。那么放到多人共同开发的我的项目中,除了封装一个办法,还有什么更好的方法来对立实现操作日志的记录?上面就要探讨下在 Java 中,常见的操作日志实现形式。

当你须要给一个大型零碎从头到尾加上操作日志,那么除了上述的手动解决形式,也有很多种整体设计方案:

1. 应用 Canal 监听数据库记录操作日志

Canal 应运而生,它通过伪装成数据库的从库,读取主库发来的 binlog,用来实现 数据库增量订阅和生产业务需要。能够看我的这篇文章:

阿里开源 MySQL 中间件 Canal 疾速入门

这个形式有点是和业务逻辑齐全拆散,毛病也很大,须要应用到 MySQL 的 Binlog,向 DBA 申请就有点艰难。如果波及到批改第三方接口,那么就无奈监听他人的数据库了。所以调用 RPC 接口时,就须要额定的在业务代码中减少记录代码,毁坏了“和业务逻辑齐全拆散”这个根本准则,局限性大。

2. 通过日志文件的形式记录

log.info("订单曾经创立,订单编号:{}", orderNo)
log.info("批改了订单的配送地址:从“{}”批改到“{}”," 金灿灿小区 "," 银盏盏小区 ")

这种形式,须要手动的设定好操作日志和其余日志的区别,比方给操作日志独自的 Logger。并且,对于操作人的记录,须要在函数中额定的写入申请的上下文中。前期这种日志还须要在 SLS 等日志零碎中做额定的抽取。

3. 通过 LogUtil 的形式记录日志

LogUtil.log(orderNo, "订单创立", "小明")
LogUtil.log(orderNo, "订单创立,订单号"+"NO.11089999",  "小明")
String template = "用户 %s 批改了订单的配送地址:从“%s”批改到“%s”"
LogUtil.log(orderNo, String.format(tempalte, "小明", "金灿灿小区", "银盏盏小区"),  "小明")

这种形式会导致业务的逻辑比拟繁冗,最初导致 LogUtils.logRecord() 办法的调用存在于很多业务的代码中,而且相似 getLogContent() 这样的办法也散落在各个业务类中,对于代码的可读性和可维护性来说是一个劫难。

4. 办法注解实现操作日志

@OperationLog(bizType = "bizType", bizId = "#request.orderId", pipeline = DataPipelineEnum.QUEUE)
public Response<BaseResult> function(Request request) {// 办法执行逻辑}

咱们能够在注解的操作日志上记录固定文案,这样业务逻辑和业务代码能够做到解耦,让咱们的业务代码变得污浊起来。

美团的原文给出了注解记录日志的具体架构设计计划,并且贴出了局部源码。然而文中并没有残缺的开源我的项目,因为本人也很感兴趣,并且公司的业务正好也有相似需要,所以我花了点工夫,实现了一版最繁难的版本,反对将操作日志传递到音讯队列中。

实战:通过注解实现操作日志的记录

大楼不是一天建成的,美团博客中形容的计划应该在公司外部曾经十分成熟了,我也没有那么多精力一口气吃成一个瘦子,咱们从最根底的版本写起。

我给本人的这个我的项目,或者说依赖包起名为 log-record-starter,一方面遵循 springboot-starter 命名标准,一方面也表明我的项目的用途,记录日志。

开启我的项目之前,先问问本人

Q:你这个依赖包,又是一个冗余的造轮子吧?市面上这种货色是不是曾经够多了?

A:本着有现成轮子绝不造轮子的准则,我在 Github 和其余网站进行了一系列的相干搜寻,Github 有几个相似的实现我的项目,不过都以集体实现为主,没有一个具备肯定影响力的成熟我的项目。基于我在本人的业务我的项目中领有理论的场景需要,并且目前还没有满足我需要的现成可接入依赖,我才开始这个依赖包的代码编写。

Q:我用了你这个依赖包,是不是很简单?之后你不保护了的话,是不是坑咱们这些吃螃蟹的?

A:依赖包的保护问题始终是一个大问题,本着最小依赖,尽量可扩大的准则。本库特点如下:

  • 应用 SpringBoot Starter,接入只须要简略引入一个依赖。
  • 通过 Spring Spel 表达式拿到参数,对你的业务逻辑没有侵入性。
  • 默认应用 RabbitMq 传递日志音讯,日志操作解耦。
  • 之后会引入其余数据源,例如 Kafka 等(毕竟还要给三歪的我的项目用)。

好了,这就是我想说在后面的话。上面就是该项目标应用介绍和利用场景介绍。

Log-record-starter 一句话介绍

本我的项目反对用户应用注解的形式从办法中获取操作日志,并推送到指定数据源

只须要简略的加上一个 @OperationLog 便能够将办法的参数,返回后果甚至是异样堆栈通过音讯队列发送进来,对立解决。

@OperationLog(bizType = "bizType", bizId = "#request.orderId", pipeline = DataPipelineEnum.QUEUE)
public Response<BaseResult> function(Request request) {// 办法执行逻辑}

应用办法

只须要简略的三步:

第一步: SpringBoot 我的项目中引入依赖

<dependency>
    <groupId>cn.monitor4all</groupId>
    <artifactId>log-record-starter</artifactId>
    <version>1.0.0</version>
</dependency>

这里先打断一下,因为 Maven 公共仓库,是寰球惟一托管的,集体开发的我的项目要提交下来,须要简单的审核流程,我搞了一会没搞定,就先将包传到了 Github Package 上(理论就是 Github 的公有 Maven 库),所以大家引入依赖后,是不会间接拉到包的,须要配置下你的 Maven settings.xml 文件。(之后我必定想方法发到公共仓库,呜呜呜~

配置很简略,两步,一步是去 Github 登录,到本人的 Settings 中,申请一个 token,拿到一串字符串。

第二步,找到你的 settings.xml 文件,增加上:

activeProfiles>
    <activeProfile>github</activeProfile>
  </activeProfiles>

  <profiles>
    <profile>
      <id>github</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>https://repo1.maven.org/maven2</url>
        </repository>
        <repository>
          <id>github</id>
          <url>https://maven.pkg.github.com/OWNER/REPOSITORY</url>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <servers>
    <server>
      <id>github</id>
      <username> 这里填写你的 Github 用户名 </username>
      <password> 这里填写你方才申请的 token</password>
    </server>
  </servers>

还搞不定的同学,这里是 Github 官网中文教程:

https://docs.github.com/en/pa…

重启下你的 IDEA,能看到上面这个,应该你的 settings.xml 失效了。

目前我的版本号是 1.0.0,之后会更新,将来最新版本号在我仓库查问:

https://github.com/qqxx6661/l…

第二步: 在 Spring 配置文件中增加 RabbitMq 数据源配置

在本人公司里因为阿里封装了本人的 MQ 叫做 MetaQ,并没有对外开源,所以这里先接入了 RabbitMQ,也算是比拟通用,图个不便。将来会接其余数据源。RabbitMq 的装置在这里不开展了,切实是不想把篇幅拉得太大,大家能够自行谷歌下,比方“Docker 装置 RabbitMq”相似的文章,几分钟就能够设置装置好。

log-record.rabbitmq.host=localhost
log-record.rabbitmq.port=5672
log-record.rabbitmq.username=admin
log-record.rabbitmq.password=xxxxxxxx
log-record.rabbitmq.queue-name=logrecord
log-record.rabbitmq.routing-key=
log-record.rabbitmq.exchange-name=logrecord

第三步: 在你本人的我的项目中,在须要记录日志的办法上,增加注解。

@OperationLog(bizType = "bizType", bizId = "#request.orderId", pipeline = DataPipelineEnum.QUEUE)
public Response<BaseResult> function(Request request) {// 办法执行逻辑}
  • (必填)bizType:业务类型
  • (必填)bizId:惟一业务 ID(反对 SpEL 表达式)
  • (必填)pipeline:数据管道,目前只有 QUEUE 一个数据管道,后续可思考接入更多数据源
  • (非必填)msg:须要传递的其余数据(反对 SpEL 表达式)
  • (非必填)tag:自定义标签

代码工作原理

因为采纳的是 SpringBoot Starter 形式,所以只有你是用的是 SpringBoot,会主动扫描到依赖包中的类,并主动通过 Spring 进行配置和治理。

该注解通过在切面中解析 SpEL 参数(啥事 SpEL?快去谷歌下,之后要讲),将数据发往数据源。目前仅反对 RabbitMq,发送的音讯体如下:

办法解决失常发送音讯体:

[LogDTO(logId=3771ff1e-e5ff-4251-a534-31dab5b666b3, bizId=str, bizType=testType1, exception=null, operateDate=Sat Nov 06 20:08:54 CST 2021, success=true, msg={"testList":["1","2","3"],"testStr":"str"}, tag=operation)]

办法解决异样发送音讯体:

[LogDTO(logId=d162b2db-2346-4144-8cd4-aea900e4682b, bizId=str, bizType=testType1, exception=testError, operateDate=Sat Nov 06 20:09:24 CST 2021, success=false, msg={"testList":["1","2","3"],"testStr":"str"}, tag=operation)]

LogDTO 是定义的音讯构造:

logId:生成的 UUID
bizId:注解中传递的 bizId
bizType:注解中传递的 bizType
exception:若办法执行失败,写入执行的异样信息
operateDate: 操作执行的以后工夫
success:形式是否执行胜利
msg:注解中传递的 tag
tag:注解中传递的 tag

我还加上了反复注解的反对,能够在一个办法上同时加多个 @OperationLog,下图是最终应用成果,能够看到,有几个 @OperationLog,就能同时发送多条日志:

我的项目具体的实现原理和细节,放在下一篇文章具体讲。(必定会填坑)

利用场景

以下列举了一些理论的利用场景,包含我业务中理论应用,并且曾经上线应用的场景。

一、特定操作记录日志:如文章最下面一张 CRM 零碎的图形容的那样,在用户进行了编辑操作后,拿到用户操作的数据,执行日志写入。

二、特定操作触发告诉:因为我的业务是接手了好几个仓库,并且这几个仓库的操作串成了一条实现链路,我须要在链路的某个节点触发给用户的揭示,如果写硬编码也能够实现,然而远不如在办法上应用注解发送音讯来得不便。例如下方在下单办法调用后发送音讯。

三、特定操作更新数据表:我的业务中,几个零碎相互吞吐数据,订单的一部分数据存留在内部零碎里,咱们最终目标想要将其中一个零碎代替掉,所以须要拦挡他们的数据,恰好几个零碎是应用 LINK 作为网关的,咱们将数据申请拦挡一层,并将拦挡的办法应用该二方库进行全副参数的发送,将数据同步写入咱们本人的数据库中,实现”双写“。

四、跨多利用数据聚合操作:和”三“相似,在多个利用中,如果须要做行为雷同的业务逻辑,齐全能够在各个系统中将数据发送到同一个音讯队列中,再进行对立解决。

附录:Demo

最初,必定有小伙伴心愿有一个残缺的应用 Demo,这就奉上!

残缺 Demo 我的项目:

https://github.com/qqxx6661/s…

log-record-starter:

https://github.com/qqxx6661/l…

总结

本文带大家理解了操作日志在 Java 中的几种实现形式,并且初步介绍了本人的实现代码,在之后的文章里,我会把实现的细节,包含如何部署到 Maven 仓库等一一和大家唠唠,记得留下你的点赞和珍藏~

我是目前在阿里搬砖的工程师蛮三刀酱。

公众号:后端技术漫谈

继续的创作离不开你的点赞和转发分享!

退出移动版