Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何使用注解优雅的记录操作日志 | 萌新写开源 01

如何使用注解优雅的记录操作日志 | 萌新写开源 01

原创
作者头像
蛮三刀酱
修改于 2021-11-08 09:07:48
修改于 2021-11-08 09:07:48
1.7K0
举报

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

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

本文作为《萌新写开源》的开篇,先把项目成品介绍给大家,之后的文章会详细介绍,如何一步步将个人项目做成一个大家都能参与的开源项目(如何写SpringBoot Starter,如何上传到Maven仓库,如何设计和使用注解和切面等)——蛮三刀酱

本文目录:

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

什么是操作日志?

定义:操作日志主要是指对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。

以我们系统内部使用的一个CRM系统举例,里面每个联系人的资料都会有操作历史:

这些数据就是操作系统日志,这些数据通常会以结构化数据的形式存储在数据库中,对于开发来说,这种日志的代码逻辑通常是非常规律,比如读取变化前和变化后的数据,获取当前操作人和操作时间等等。

常见的操作日志实现方式

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

当你需要给一个大型系统从头到尾加上操作日志,那么除了上述的手动处理方式,也有很多种整体设计方案:

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

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

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

这个方式有点是和业务逻辑完全分离,缺点也很大,需要使用到MySQL的Binlog,向DBA申请就有点困难。如果涉及到修改第三方接口,那么就无法监听别人的数据库了。所以调用RPC接口时,就需要额外的在业务代码中增加记录代码,破坏了“和业务逻辑完全分离”这个基本原则,局限性大。

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

代码语言:txt
AI代码解释
复制
log.info("订单已经创建,订单编号:{}", orderNo)
log.info("修改了订单的配送地址:从“{}”修改到“{}”, "金灿灿小区", "银盏盏小区")

这种方式,需要手动的设定好操作日志和其他日志的区别,比如给操作日志单独的Logger。并且,对于操作人的记录,需要在函数中额外的写入请求的上下文中。后期这种日志还需要在SLS等日志系统中做额外的抽取。

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

代码语言:txt
AI代码解释
复制
LogUtil.log(orderNo, "订单创建", "小明")
LogUtil.log(orderNo, "订单创建,订单号"+"NO.11089999",  "小明")
String template = "用户%s修改了订单的配送地址:从“%s”修改到“%s”"
LogUtil.log(orderNo, String.format(tempalte, "小明", "金灿灿小区", "银盏盏小区"),  "小明")

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

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

代码语言:txt
AI代码解释
复制
@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便可以将方法的参数,返回结果甚至是异常堆栈通过消息队列发送出去,统一处理。

代码语言:txt
AI代码解释
复制
@OperationLog(bizType = "bizType", bizId = "#request.orderId", pipeline = DataPipelineEnum.QUEUE)
public Response<BaseResult> function(Request request) {
  // 方法执行逻辑
}

使用方法

只需要简单的三步:

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

代码语言:txt
AI代码解释
复制
<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,拿到一串字符串。

image-20211106162359065
image-20211106162359065

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

代码语言:txt
AI代码解释
复制
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/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry

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

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

https://github.com/qqxx6661/logRecord

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

在自己公司里由于阿里封装了自己的MQ叫做MetaQ,并没有对外开源,所以这里先接入了RabbitMQ,也算是比较通用,图个方便。未来会接其他数据源。RabbitMq的安装在这里不展开了,实在是不想把篇幅拉得太大,大家可以自行谷歌下,比如“Docker安装RabbitMq”类似的文章,几分钟就可以设置安装好。

代码语言:txt
AI代码解释
复制
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

第三步: 在你自己的项目中,在需要记录日志的方法上,添加注解。

代码语言:txt
AI代码解释
复制
@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,发送的消息体如下:

方法处理正常发送消息体:

代码语言:txt
AI代码解释
复制
[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)]

方法处理异常发送消息体:

代码语言:txt
AI代码解释
复制
[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是定义的消息结构:

代码语言:txt
AI代码解释
复制
logId:生成的UUID
bizId:注解中传递的bizId
bizType:注解中传递的bizType
exception:若方法执行失败,写入执行的异常信息
operateDate:操作执行的当前时间
success:方式是否执行成功
msg:注解中传递的tag
tag:注解中传递的tag

我还加上了重复注解的支持,可以在一个方法上同时加多个@OperationLog,下图是最终使用效果,可以看到,有几个@OperationLog,就能同时发送多条日志:

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

应用场景

以下罗列了一些实际的应用场景,包括我业务中实际使用,并且已经上线使用的场景。

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

二、特定操作触发通知:由于我的业务是接手了好几个仓库,并且这几个仓库的操作串成了一条完成链路,我需要在链路的某个节点触发给用户的提醒,如果写硬编码也可以实现,但是远不如在方法上使用注解发送消息来得方便。例如下方在下单方法调用后发送消息。

三、特定操作更新数据表:我的业务中,几个系统互相吞吐数据,订单的一部分数据存留在外部系统里,我们最终目标想要将其中一个系统替代掉,所以需要拦截他们的数据,恰好几个系统是使用LINK作为网关的,我们将数据请求拦截一层,并将拦截的方法使用该二方库进行全部参数的发送,将数据同步写入我们自己的数据库中,实现”双写“。

四、跨多应用数据聚合操作:和”三“类似,在多个应用中,如果需要做行为相同的业务逻辑,完全可以在各个系统中将数据发送到同一个消息队列中,再进行统一处理。

附录:Demo

最后,肯定有小伙伴希望有一个完整的使用Demo,这就奉上!

完整Demo项目:

https://github.com/qqxx6661/systemLog

log-record-starter:

https://github.com/qqxx6661/logRecord

总结

本文带大家了解了操作日志在Java中的几种实现方式,并且初步介绍了自己的实现代码,在之后的文章里,我会把实现的细节,包括如何部署到Maven仓库等一一和大家唠唠~

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
log-record正式版本发布:自定义函数、手动传递上下文 、本地监听支持
之前写了两篇文章,来介绍我的log-record开源项目(优雅记录操作日志)是如何诞生的。
Rude3Knife的公众号
2022/01/20
1.3K0
log-record正式版本发布:自定义函数、手动传递上下文 、本地监听支持
如何优雅地记录操作日志?
操作日志几乎存在于每个系统中,而这些系统都有记录操作日志的一套 API。操作日志和系统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?上面这些都是本文要回答的问题。我们主要围绕着如何“优雅”地记录操作日志展开描述,希望对从事相关工作的同学能够有所帮助或者启发。
美团技术团队
2021/10/12
2.3K2
如何优雅地记录操作日志?
一个注解,搞定 SpringBoot 操作日志
tenant是代表租户的标识,一般一个服务或者一个业务下的多个服务都写死一个 tenant 就可以
业余草
2021/03/04
1K0
一个注解,搞定 SpringBoot 操作日志
SpringBoot 操作日志
tenant 是代表租户的标识,一般一个服务或者一个业务下的多个服务都写死一个 tenant 就可以
南风
2021/08/24
1.1K0
SpringBoot 操作日志
读后感 | 美团技术《如何优雅地记录操作日志》
《如何优雅地记录操作日志》是美团技术团队2021年最受欢迎的一篇技术文章,文章很有深度,强烈建议大家去品读一番。
程序猿杜小头
2022/12/01
2.4K0
读后感 | 美团技术《如何优雅地记录操作日志》
springboot aop 自定义注解方式实现完善日志记录(完整源码)
主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值。
用户1518699
2019/11/29
3.4K0
我用注解实现接口的操作流水日志
在项目中,我们会需要获取接口的操作日志。比如获取接口的接口名、操作人,接口运行时间、所属的服务、接口的类型(增删改查)等等。初级的做法是在接口方法执行完后将这些操作记录存入库中,这段代码写在接口中,但是这样违反了设计原则中的单一职责原则。常用的做法是使用AOP来做,在运行时动态的插入日志记录的代码。这里我是用注解来做。
Lvshen
2022/05/05
5140
我用注解实现接口的操作流水日志
使用 SpringBoot AOP 记录操作日志、异常日志
平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能;我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发生的异常进行统计,从而改进我们的项目,要是能做个功能专门来记录操作日志和异常日志那就好了, 当然我们肯定有方法来做这件事情,而且也不会很难,我们可以在需要的方法中增加记录日志的代码,和在每个方法中增加记录异常的代码,最终把记录的日志存到数据库中。听起来好像很容易,但是我们做起来会发现,做这项工作很繁琐,而且都是在做一些重复性工作,还增加大量冗余代码,这种方式记录日志肯定是不可行的。
用户1516716
2020/10/23
6.9K1
使用 SpringBoot AOP 记录操作日志、异常日志
spring-boot使用aop进行日志记录
运行项目,然后访问上述controller,然后查看控制台和数据库中相应的表是否有对应数据
earthchen
2020/09/24
9490
Grace:优雅高效的的记录业务操作日志
Grace[ɡreɪs]是一款业务操作日志记录框架,让我们使用更优雅方式来记录有效的、可读性高的操作日志。
恒宇少年
2022/01/20
6720
如何使用SpringBoot AOP 记录操作日志、异常日志?
平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能;我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发生的异常进行统计,从而改进我们的项目,要是能做个功能专门来记录操作日志和异常日志那就好了。
全栈程序员站长
2021/06/29
8.7K3
springboot aop 自定义注解方式实现一套完善的日志记录
主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值。
java架构师
2019/03/01
9320
操作日志数据治理实战
网上采购流程中,多个系统组成了采购交易生态圈,为用户提供线上采购服务能力。而每个系统几乎都离不开日志记录。
政采云前端团队
2023/09/01
2670
操作日志数据治理实战
手把手教你提交Jar包到Maven公共仓库 | 萌新写开源02
在上一篇文章中,我介绍了自己的SpringBoot Starter项目,可以让我们使用注解的方式轻松地获取操作日志,并推送到指定数据源。
蛮三刀酱
2021/12/14
1.2K0
手把手教你提交Jar包到Maven公共仓库 | 萌新写开源02
springboot集成redis(mybatis、分布式session)
安装Redis请参考:《CentOS快速安装Redis》 一、springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <depen
名山丶深处
2018/06/20
9870
如何提交自己的项目到Maven公共仓库 | 萌新学开源 02
在上一篇文章中,我介绍了自己的SpringBoot Starter项目,可以让我们使用注解的方式轻松地获取操作日志,并推送到指定数据源。
Rude3Knife的公众号
2021/12/13
2.3K0
如何提交自己的项目到Maven公共仓库 | 萌新学开源 02
SpringBoot AOP 自定义注解异步监听方式实现日志记录(附源码)
主要记录的信息有: 操作人,操作IP,方法名,参数,消耗时间,日志类型,操作类型(操作日志和异常日志)以及增删改查记录,操作时间等。
小东啊
2019/06/26
3.2K0
SpringBoot AOP 自定义注解异步监听方式实现日志记录(附源码)
如何在 Spring Boot 中实现操作日志系统
在开发企业级应用时,记录用户操作日志是非常重要的。这不仅能帮助开发者监控系统的行为,还能在出现问题时进行追踪。在这篇文章中,我们将介绍如何在Spring Boot中开发一个完整的日志系统,记录每一步操作,如登录、创建订单、删除、查询等。
Swift社区
2024/07/02
5300
如何在 Spring Boot 中实现操作日志系统
如何优雅地Spring事务编程
在开发中,有时候我们需要对 Spring 事务的生命周期进行监控,比如在事务提交、回滚或挂起时触发特定的逻辑处理。那么如何实现这种定制化操作呢?
BookSea
2024/04/30
1780
如何优雅地Spring事务编程
手把手教你如何优雅的使用Aop记录带参数的复杂Web接口日志
不久前,因为需求的原因,需要实现一个操作日志。几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。举个例子,就比如禁言操作,日志中需要记录因为什么禁言,被禁言的人的id和各种信息。方便后期查询。
SH的全栈笔记
2019/10/20
2.2K0
推荐阅读
相关推荐
log-record正式版本发布:自定义函数、手动传递上下文 、本地监听支持
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档