# Log2Monitor
**Repository Path**: RaceK/log2monitor
## Basic Information
- **Project Name**: Log2Monitor
- **Description**: 采用注解+AOP的方式更为优雅的记录日志
- **Primary Language**: Unknown
- **License**: GPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-12-26
- **Last Updated**: 2023-02-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java, Spring, spring-aop
## README
> 本项目实现基于[美团技术博客](https://tech.meituan.com/2021/09/16/operational-logbook.html) , 参考项目 [mzt-biz-log](https://github.com/mouzt/mzt-biz-log/) 实现。
# Ⅰ. 背景
首选需要区分一下**系统日志**和**操作日志**的区别:
1. 系统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志。
2. 主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情,或者是客服对工单的处理记录信息。
举个最简单的例子:
```java
// 系统日志
log.info("订单创建");
log.error("订单创建异常: ", throwable);
// 操作日志,会存在持久化操作
String template = "用户%s修改了订单的配送地址:从“%s”修改到“%s”";
LogUtil.log(orderNo, String.format(tempalte, "小明", "金灿灿小区", "银盏盏小区"), "小明");
```
# Ⅱ. 实现逻辑
对于记录日志,最简单的方式就是直接在业务代码中增加一段逻辑以保存操作日志,通过封装方法能更为简洁。但是随着业务的发展,记录操作日志放在业务代码中会导致业务的逻辑非常繁杂,对于封装方法的调用会存在于很多的业务代码中,也会散落在各个业务类和方法里面,对于代码的维护性和可读性就是一个灾难。
所以为了解决这个问题,一般会推荐使用 **AOP + 注解** 的方式,让记录操作日志的代码和业务代码**解耦**,也让代码变得更为优雅。
同时,为了保证**日志内容可以动态自定义**,也可以使用 Spring 中的 **(Spring Expression Language,Spring表达式语言)** 来应用方法上面的参数,让参数可以动态地填充到定义好的模板中。
因此,操作日志的记录方式可以大致分为以下几个模块: AOP 拦截、注解解析、模板解析、日志持久化、Springboot Starter;同时,可以提供一些拓展点用于自定义日志的加工:自定义后置处理。
至此,已经搭建好了基本的框架。
---
接下来,需要注意到一个问题:如何解决模板中的参数无法通过方法参数获得?
例如业务上对物流订单的地址信息进行了变更,那么业务上要求展示操作日志为:“修改了订单的配送地址:从 xx 修改到 yy”,那么为了显示旧的地址,我们需要在方法参数上增加一个字段 `String oldAddress`,但是显然,这种方式是会逼死强迫症的。
因此需要增加一种方式,传入方法参数列表中不存在的内容:把这个参数放到**操作日志的线程上下文**中,供注解上的模板使用。
尽管依然需要在业务代码中增加一行添加参数的逻辑,所以这个方案还不完全优雅,但是我们可以利用 SpEL 强大的解析能力,在模板中增加自定义函数,这样只需要在数据修改之前通过自定义函数将数据记录下来,就可以避免代码的耦合,非常优雅。
通过**大括号**把 Spring 的 SpEL 表达式包裹起来,这样做的好处:一是把 SpEL 和自定义函数区分开便于解析;二是如果模板中不需要 SpEL 表达式解析可以容易的识别出来,减少 SpEL 的解析提高性能。
```java
@OperationLog(
content = "修改了订单的配送地址:从“{queryOldAddress{#request.deliveryOrderNo()}}”, 修改到“#request.address”",
bizNo="#request.orderNo"
)
public void modifyAddress(updateDeliveryRequest request){
// 更新派送信息 电话,收件人、地址
doUpdate(request);
}
```