本文讨论如何优雅的记录操作日志,实现了一个SpringBootStarter(取名log-record-starter),方便使用注解记录操作日志,并推送日志数据到指定的数据管道(消息队列等)本文灵感来自于美团技术团队的文章:如何优雅地记录操作日志?.本文中使用的部分定义、描述和示例均来自美团点评原文,请注意。作为《萌新写开源》的开篇,本文先向大家介绍完成的项目,后面的文章会一步步详细介绍如何将个人项目做成大家可以参与的开源项目(如何编写SpringBootStarter,如何上传到Maven仓库,如何设计使用注解和切面等),请多多点赞支持,这就是我更新的动力。请放心,公众号会继续更新的,我没有忘记密码。:)——满三道江本文内容:什么是操作日志?Java中常见的操作日志实现方法:通过注解实现操作日志记录什么是操作日志?定义:操作日志主要是指记录对象新增或修改后的新增或修改。操作日志要求可读性强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道什么时候发生了什么。再比如,客服会记录工单的处理信息。以我们系统内部使用的一个CRM系统为例,其中每个联系人的数据都会有一个操作历史:这些数据是操作系统日志,这些数据通常以结构化数据的形式存储在数据库中。对于开发来说,这种日志的代码逻辑通常是非常有规律的,比如读取变化前后的数据,获取当前的操作者和操作时间等等。常见的操作日志实现方式在小项目中,这种日志操作通常是通过提供一个接口或者整个日志服务来实现的。那么在一个多人共同开发的项目中,除了封装一个方法,有没有更好的方式来统一实现操作日志的记录呢?下面讨论Java中操作日志的常见实现。当你需要在一个大系统中自始至终添加操作日志时,除了上述手动处理方式外,还有很多整体设计方案:1、使用Canal监控数据库来记录操作日志。从库读取主库发送的binlog,实现数据库增量订阅消费业务需求。可以看我的文章:阿里开源的MySQL中间件Canal快速入门这种方式完全脱离了业务逻辑,弊端很大。需要用到MySQL的Binlog,申请DBA有点难度。如果涉及到修改第三方接口,那么就不可能监控到别人的数据库了。因此,在调用RPC接口时,需要在业务代码中增加额外的记录代码,破坏了“与业务逻辑完全分离”的基本原则,具有很大的局限性。2.recordlog.info("订单已创建,订单号:{}",orderNo)log.info("订单的收货地址已修改:由“{}”改为“{}”,"金灿灿社区”、“银盏盏社区”)这种方式需要手动设置操作日志和其他日志的区别,比如操作日志需要单独的Logger。并且,对于操作者的记录,需要在function在追加写请求的上下文中。后期需要在SLS等日志系统中追加提取这种日志。3.通过LogUtil记录日志LogUtil.log(orderNo,"ordercreation","Xiaoming")LogUtil.log(orderNo,"订单创建,订单号"+"NO.11089999","小明")Stringtemplate="用户%s修改了订单的收货地址:从"%s"到"%s""LogUtil.log(orderNo,String.format(tempalte,"小明","金灿灿区","银盏展区"),"小明")会导致业务逻辑复杂,最终会导致LogUtils.logRecord()方法调用存在于很多业务代码中,像getLogContent()这样的方法也散落在各个业务类中,对于代码的可读性和可维护性来说是一场灾难。4.方法注解实现操作日志@OperationLog(bizType="bizType",bizId="#request.orderId",pipeline=DataPipelineEnum.QUEUE)publicResponsefunction(Requestrequest){//方法执行逻辑}We可以在注解的操作日志上记录固定文案,让业务逻辑和业务代码解耦,让我们的业务代码纯净。美团原文给出了注解日志的详细架构设计方案,贴出了部分源码。但是本文没有完整的开源项目。由于自己也很感兴趣,而且公司的业务刚好也有类似的需求,所以花了点时间实现了最简单的版本,支持将操作日志传输到消息队列中。.实战:实现通过注解记录操作日志,非一日之功。美团博客里描述的方案在公司内部应该已经很成熟了,我也没那么大的精力一口气吃掉一个胖子。动笔。我将我的项目或依赖包命名为log-record-starter。一方面遵循了springboot-starter的命名规范,另一方面也说明了项目的用处,记录日志。在开始项目之前,问问自己Q:你的依赖包又是一个多余的轮子,对吧?市场上有足够的这种东西吗?A:本着有现成的轮子就不造轮子的原则,我在Github等网站上进行了一系列的相关搜索。Github上有几个类似的实现项目,但都是基于个人实现,没有一个有一定影响力的强大成熟项目。基于自己业务项目中有实际场景需求,没有现成的可访问依赖满足自己的需求,所以才开始写这个依赖包的代码。Q:我用了你们的依赖包,是不是很复杂?之后再不保养,是不是在坑我们这些吃螃蟹的人?A:依赖包的维护一直是个大问题,本着最小依赖和尽可能扩展的原则。这个库的特点是:使用SpringBootStarter,访问只需要简单的引入一个依赖。通过SpringSpel表达式获取参数不会侵入您的业务逻辑。默认使用RabbitMq来传递日志消息,日志操作是解耦的。后面会介绍其他的数据源,比如Kafka等(毕竟是三外项目用的)。嗯,这就是我想提前说的。下面介绍一下该项目的使用和应用场景。Log-record-starter一句话介绍本项目支持用户使用注解从方法中获取操作日志,并推送到指定的数据源。只需添加一个@OperationLog即可返回方法参数、返回结果,甚至异常。栈通过消息队列发送出去,统一处理。@OperationLog(bizType="bizType",bizId="#request.orderId",pipeline=DataPipelineEnum.QUEUE)publicResponsefunction(Requestrequest){//方法执行逻辑}只需要三个简单的步骤就可以使用方法:第一步:在SpringBoot项目中引入依赖cn.monitor4alllog-record-starter1.0.0这里先打断一下,由于Maven公共仓库是全球唯一托管的,个人开发的项目需要提交,需要一个复杂的审核过程。privateMavenlibrary),所以你引入依赖后,你不会直接拉包,你需要配置你的Mavensettings.xml文件。(以后一定会想办法发到公共仓库的,呜呜呜~)配置很简单,两步,一步是登录Github,去自己的Settings,申请token,并得到一串字符串。第二步是找到你的settings.xml文件并添加:>中央https://repo1.maven.org/maven2githubhttps://maven.pkg。github.com/OWNER/REPOSITORYtruegithub在这里填写你的Github用户名在这里填写你刚刚申请的token还不确定的同学,在这里是Github官方中文教程:https://docs.gitub.com/en/pa...重启你的IDEA,你可以看到如下,你的settings.xml应该已经生效了。目前我的版本号是1.0.0,以后会更新。以后最新的版本号在我的Warehouse查询:https://github.com/qqxx6661/l...第二步:在自己公司的Spring配置文件中添加RabbitMq数据源配置因为阿里封装了自己的MQ称为MetaQ,不开源。所以这里先连接RabbitMQ,比较通用方便。将来会添加其他数据源。RabbitMq的安装这里就不展开了。我真的不想把空间拉得太长。可以自己google一下,比如《Docker安装RabbitMq》类似的文章,几分钟就可以搭建安装好。log-record.rabbitmq.host=localhostlog-record.rabbitmq.port=5672log-record.rabbitmq.username=adminlog-record.rabbitmq.password=xxxxxxxxlog-record.rabbitmq.queue-name=logrecordlog-record.rabbitmq.routing-key=log-record.rabbitmq.exchange-name=logrecord第三步:在自己的项目中,为需要记录日志的方法添加注解。@OperationLog(bizType="bizType",bizId="#request.orderId",pipeline=DataPipelineEnum.QUEUE)publicResponsefunction(Requestrequest){//方法执行逻辑}(required)bizType:businesstype(Required)bizId:业务唯一ID(支持SpEL表达式)(必填)pipeline:数据管道,目前只有QUEUE一条数据管道,以后可能会考虑更多的数据源(非必填)msg:其他需要的deliveredData(supportsSpELexpressions)(notrequired)标签:自定义标签代码的工作原理是SpringBoot的Starter方法,所以只要你在使用SpringBoot,它就会自动扫描依赖包中的类,并通过Spring用于配置和管理。这个注解是通过解析切面中的SpEL参数将数据发送到数据源的(什么是SpEL?自己去谷歌,后面再说)。目前只支持RabbitMq,发送消息体如下:方法处理正常发送消息体:[LogDTO(logId=3771ff1e-e5ff-4251-a534-31dab5b666b3,bizId=str,bizType=testType1,exception=null,operateDate=2021年11月6日星期六20:08:54CST,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=SatNov0620:09:24CST2021,success=false,msg={"testList":["1","2","3"],"testStr":"str"},tag=operation)]LogDTO是定义的消息结构:logId:generatedUUIDbizId:passed注解中bizIdbizType:注解中传入的bizTypeexception:如果方法执行失败,写入执行异常信息operateDate:当前操作时间executionsuccess:方法是否执行成功msg:注解标签中传入的tag:我还添加了在注解中传递的标签,支持重复注解,可以在一个方法中同时添加多个@OperationLog。下图是最终的使用效果。可以看到,加上几个@OperationLog,可以同时发送多条日志:具体实现原理和项目细节,下篇文章再详细说。(一定会填坑的)应用场景下面列举一些实际的应用场景,包括我业务中实际使用过的和线上使用过的场景。1、具体操作记录日志:如文首CRM系统图所述,用户进行编辑操作后,获取用户操作的数据,写入日志。2、具体操作触发通知:由于我的业务接管了几个仓库,而这些仓库的操作都连接成一个完成链接,所以我需要在链接的某个节点触发提醒给用户。如果写Hardcoding也可以实现,但是远不如在方法上使用注解来发送消息方便。例如,下面的消息是在调用order方法后发送的。3、通过特定操作更新数据表:在我的业务中,几个系统相互传递数据,部分订单数据存储在外部系统中。我们的最终目标是更换其中一个系统,所以我们需要拦截他们的数据。一些系统使用LINK作为网关。我们对数据请求进行一级拦截,通过拦截方式使用二方库发送所有参数,将数据同步写入自己的数据库,实现“双写”。4、跨多应用数据聚合操作:与“3”类似,在多个应用中,如果需要做具有相同行为的业务逻辑,可以将数据发送到各个系统中的同一个消息队列,然后进行统一处理.附录:Demo最后,一定有小伙伴想要完整的demo,这里有!完整Demo工程:https://github.com/qqxx6661/s...几种实现方法,初步介绍一下自己的实现代码。在后面的文章中,我会和大家聊聊实现的细节,包括如何部署到Maven仓库。记得留下你的喜欢和收藏~我是漫三道江,目前在阿里搬砖的工程师。公众号:浅谈后端技术持续的创作离不开您的点赞转发分享!