Project Icon

log-record

基于Java注解的轻量级操作日志记录框架

log-record是一个基于Java注解的操作日志记录框架,支持SpEL表达式和自定义函数。该项目提供无侵入式日志记录,支持实体类对比、条件注解等功能。适用于SpringBoot 1/2/3版本,支持自定义日志处理或发送至消息队列。通过简单配置即可实现复杂的日志记录需求,有助于提升代码质量。

log-record


注意:本仓库最初灵感来源于美团技术博客 ,若您需要寻找的是原文中作者的代码仓库,可以跳转这里 。本仓库从零实现了原文中描述的大部分特性,并吸取大量生产环境实践和内外网用户反馈,随着持续稳定的维护和更新,期望给用户提供更多差异化的功能。

通过Java注解优雅的记录操作日志,并支持SpEL表达式,自定义上下文,自定义函数,实体类DIFF等功能,最终日志可由用户自行采集并处理,或推送至预配置的消息队列,支持SpringBoot1&2&3(JDK8~JDK21)。

采用SpringBoot Starter的方式,只需一个依赖,一句注解,日志轻松记录,不侵入业务逻辑:

@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "'用户' + #queryUserName(#request.userId) + '修改了订单的跟进人:从' + #queryOldFollower(#request.orderId) + '修改到' + #request.newFollower")
public Response<T> function(Request request) {
  // 业务执行逻辑
}

SpringBoot1&SpringBoot2(JDK8+)请引用:

<dependency>
    <groupId>cn.monitor4all</groupId>
    <artifactId>log-record-starter</artifactId>
    <version>{最新版本号}</version>
</dependency>

SpringBoot3(JDK17+)请引用:

<dependency>
    <groupId>cn.monitor4all</groupId>
    <artifactId>log-record-springboot3-starter</artifactId>
    <version>{最新版本号}</version>
</dependency>

最新版本号请查阅Maven公共仓库

项目背景

大家一定见过下图的操作日志:

在代码层面,如何优雅的记录上面的日志呢?

能想到最粗暴的方式,封装一个操作日志记录类,如下:

String template = "用户%s修改了订单的跟进人:从“%s”修改到“%s”"
LogUtil.log(orderNo, String.format(tempalte, "张三", "李四", "王五"),  "张三")

这种方式会导致业务代码被记录日志的代码侵入,对于代码的可读性和可维护性来说是一个灾难。

这个方式显然不够优雅,让我们试试使用注解:

@OperationLog(bizType = "'followerChange'", bizId = "'20211102001'", msg = "'用户 张三 修改了订单的跟进人:从 李四 修改到 王五'")
public Response<T> function(Request request) {
  // 业务执行逻辑
}

日志的记录被放到了注解,对业务代码没有侵入。

但是新的问题来了,我们该如何把订单ID、用户信息、数据库里的旧地址、函数入参的新地址传递给注解呢?

SpringSpEL表达式(Spring Expression Language 可以帮助我们,通过引入SpEL表达式,我们可以获取函数的入参。这样我们就可以对上面的注解进行修改:

  • 订单ID:#request.orderId
  • 新地址"王五":#request.newFollower
@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "'用户 张三 修改了订单的跟进人:从 李四 修改到' + #request.newFollower")
public Response<T> function(Request request) {
  // 业务执行逻辑
}

如此一来,订单ID和地址的新值就可以通过解析入参动态获取了。

问题还没有结束,通常我们的用户信息(user),以及老的跟进人(oldFollower),是需要在方法中查询后才能获取,入参里一般不会包含这些数据。

解决方案也不是没有,我们创建一个可以保存上下文的LogRecordContext变量,让用户手动传递代码中计算出来的值,再交给SpEL解析 ,代码如下

@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "'用户' + #userName + '修改了订单的跟进人:从' + #oldFollower + '修改到' + #request.newFollower")
public Response<T> function(Request request) {
  // 业务执行逻辑
  ...
  // 手动传递日志上下文:用户信息 地址旧值
  LogRecordContext.putVariable("userName", queryUserName(request.getUserId()));
  LogRecordContext.putVariable("oldFollower", queryOldFollower(request.getOrderId()));
}

什么?你说这不就又侵入了业务逻辑了么?

确实是的,不过这种方法足够便捷易懂,并不会有什么理解的困难。

但是对于有“强迫症”的同学,这样的实现还是不够优雅,我们可以用SpEL支持的自定义函数,解决这个问题。

SpEL支持在表达式中传入用户自定义函数,我们将queryUserNamequeryOldFollower这两个函数提前放入SpEL的解析器中,SpEL在解析表达式时,会执行对应函数。

最终,我们的注解变成了这样,并且最终记录了日志:

@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "'用户' + #queryUserName(#request.userId) + '修改了订单的跟进人:从' + #queryOldFollower(#request.orderId) + '修改到' + #request.newFollower")
public Response<T> function(Request request) {
  // 业务执行逻辑
}

用户 张三 修改了订单的跟进人:从 李四 修改到 王五

以上便是本库的大致实现原理。

项目介绍

本库帮助你通过注解优雅地记录项目中的操作日志,对业务代码无侵入。

本项目特点:

  • 快速接入:使用Spring Boot Starter实现,用户直接在pom.xml引入依赖即可使用
  • 业务无侵入:无需侵入业务代码,日志切面发生任何异常不会影响原方法执行
  • SpEL解析:支持SpEL表达式
  • 实体类Diff:支持相同甚至不同类对象的Diff
  • 条件注解:满足Condition条件后才记录日志,通过SpEL进行解析
  • 自定义上下文:支持手动传递键值对,通过SpEL进行解析
  • 自定义函数:支持注册自定义函数,通过SpEL进行解析
  • 全局操作人ID:自定义操作人ID获取逻辑
  • 指定日志数据管道:自定义操作日志处理逻辑(写数据库,TLog等..)
  • 支持重复注解:同一个方法上可以写多个操作日志注解
  • 支持自动重试和兜底处理:支持配置重试次数和处理失败兜底逻辑SPI
  • 支持控制切面执行时机(方法执行前后)
  • 支持自定义执行成功判断
  • 支持非注解方式手动记录日志
  • 自定义消息线程池
  • 更多特性等你来发掘...

日志实体(LogDTO)内包含:

logId:生成的UUID
bizId:业务唯一ID
bizType:业务类型
exception:函数执行失败时写入异常信息
operateDate:操作执行时间
success:函数是否执行成功
msg:日志内容
tag:自定义标签
returnStr: 方法执行成功后的返回值(字符串或JSON化实体)
executionTime:方法执行耗时(单位:毫秒)
extra:额外信息
operatorId:操作人ID
List<diffDTO>: 实体类对象Diff数据,包括变更的字段名,字段值,类名等

日志实体复杂示例:

{
  "bizId":"1",
  "bizType":"testObjectDiff",
  "executionTime":0,
  "extra":"【用户工号】从【1】变成了【2】 【name】从【张三】变成了【李四】",
  "logId":"38f7f417-2cc3-40ed-8c98-2fe3ee057518",
  "msg":"【用户工号】从【1】变成了【2】 【name】从【张三】变成了【李四】",
  "operateDate":1651116932299,
  "operatorId":"操作人",
  "returnStr":"{\"id\":1,\"name\":\"张三\"}",
  "success":true,
  "exception":null,
  "tag":"operation",
  "diffDTOList":[
    {
      "diffFieldDTOList":[
        {
          "fieldName":"id",
          "newFieldAlias":"用户工号",
          "newValue":2,
          "oldFieldAlias":"用户工号",
          "oldValue":1
        },
        {
          "fieldName":"name",
          "newValue":"李四",
          "oldValue":"张三"
        }],
      "newClassAlias":"用户信息实体",
      "newClassName":"cn.monitor4all.logRecord.test.bean.TestUser",
      "oldClassAlias":"用户信息实体",
      "oldClassName":"cn.monitor4all.logRecord.test.bean.TestUser"
    },
    {
      "diffFieldDTOList":[
        {
          "fieldName":"id",
          "newFieldAlias":"用户工号",
          "newValue":2,
          "oldFieldAlias":"用户工号",
          "oldValue":1
        },
        {
          "fieldName":"name",
          "newValue":"李四",
          "oldValue":"张三"
        }],
      "newClassAlias":"用户信息实体",
      "newClassName":"cn.monitor4all.logRecord.test.bean.TestUser",
      "oldClassAlias":"用户信息实体",
      "oldClassName":"cn.monitor4all.logRecord.test.bean.TestUser"
    }]
}

使用方法

只需要简单的三步:

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

SpringBoot1&SpringBoot2(JDK8+)请引用:

<dependency>
    <groupId>cn.monitor4all</groupId>
    <artifactId>log-record-starter</artifactId>
    <version>{最新版本号}</version>
</dependency>

SpringBoot3(JDK17+)请引用:

<dependency>
    <groupId>cn.monitor4all</groupId>
    <artifactId>log-record-springboot3-starter</artifactId>
    <version>{最新版本号}</version>
</dependency>

最新版本号请查阅Maven公共仓库

推荐使用 >= 1.6.x版本

第二步: 配置日志处理方式

支持处理方式:

  1. 自定义采集处理
  2. 直接发送至RabbitMQ
  3. 直接发送至RocketMQ
  4. 直接发送至SpringCloud Stream

1. 自定义采集处理

若只需要在同一应用内处理日志信息,只需要实现接口IOperationLogGetService,便可对日志进行处理。

@Component
public class CustomFuncTestOperationLogGetService implements IOperationLogGetService {
    @Override
    public boolean createLog(LogDTO logDTO) {
        log.info("logDTO: [{}]", JSON.toJSONString(logDTO));
        return true;
    }
}

2. 直接发送至RabbitMQ

配置RabbitMQ参数

log-record.data-pipeline=rabbitMq
log-record.rabbit-mq-properties.host=localhost
log-record.rabbit-mq-properties.port=5672
log-record.rabbit-mq-properties.username=admin
log-record.rabbit-mq-properties.password=xxxxxx
log-record.rabbit-mq-properties.queue-name=logRecord
log-record.rabbit-mq-properties.routing-key=
log-record.rabbit-mq-properties.exchange-name=logRecord

3. 直接发送至RocketMQ

配置RocketMQ参数

log-record.data-pipeline=rocketMq
log-record.rocket-mq-properties.topic=logRecord
log-record.rocket-mq-properties.tag=
log-record.rocket-mq-properties.group-name=logRecord
log-record.rocket-mq-properties.namesrv-addr=localhost:9876

4. 直接发送至SpringCloud Stream

配置SpringCloud Stream参数

log-record.data-pipeline=stream
log-record.stream.destination=logRecord
log-record.stream.group=logRecord
# 为空时 默认为spring.cloud.stream.default-binder指定的Binder
log-record.stream.binder=
# rocketmq binder例子
spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876
spring.cloud.stream.rocketmq.binder.enable-msg-trace=false

第三步: 在需要记录系统操作的方法上,添加注解

@OperationLog(bizType = "'followerChange'", bizId = "#request.orderId", msg = "'用户 张三 修改了订单的跟进人:从 李四 修改到' + #request.newFollower")
public Response<T> function(Request request) {
  // 业务执行逻辑
}

进阶特性

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

稿定AI

稿定设计 是一个多功能的在线设计和创意平台,提供广泛的设计工具和资源,以满足不同用户的需求。从专业的图形设计师到普通用户,无论是进行图片处理、智能抠图、H5页面制作还是视频剪辑,稿定设计都能提供简单、高效的解决方案。该平台以其用户友好的界面和强大的功能集合,帮助用户轻松实现创意设计。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号