{"id":20482938,"url":"https://github.com/lzhpo/logger-spring-boot-starter","last_synced_at":"2025-09-25T03:30:44.624Z","repository":{"id":234677327,"uuid":"786489416","full_name":"lzhpo/logger-spring-boot-starter","owner":"lzhpo","description":"一款支持复杂场景下的操作日志记录器，不侵入业务代码，支持SpringEL表达式和自定义函数，异步监听日志事件，注解中的SpringEL表达式支持在IDEA中自动高亮以及提示，更多用法等你发掘...","archived":false,"fork":false,"pushed_at":"2024-11-29T15:57:55.000Z","size":454,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-29T16:38:30.662Z","etag":null,"topics":["logger","logging","springboot"],"latest_commit_sha":null,"homepage":"http://www.lzhpo.com","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lzhpo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-14T16:12:04.000Z","updated_at":"2024-11-29T15:57:59.000Z","dependencies_parsed_at":"2024-04-25T23:32:55.993Z","dependency_job_id":"07f32f48-cbf1-4445-87fb-b3ad965c594a","html_url":"https://github.com/lzhpo/logger-spring-boot-starter","commit_stats":null,"previous_names":["lzhpo/logger"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Flogger-spring-boot-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Flogger-spring-boot-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Flogger-spring-boot-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Flogger-spring-boot-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lzhpo","download_url":"https://codeload.github.com/lzhpo/logger-spring-boot-starter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234147907,"owners_count":18786934,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["logger","logging","springboot"],"created_at":"2024-11-15T16:15:17.569Z","updated_at":"2025-09-25T03:30:39.288Z","avatar_url":"https://github.com/lzhpo.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](https://img.shields.io/badge/JDK-1.8+-success.svg)\n![](https://maven-badges.herokuapp.com/maven-central/com.lzhpo/logger-spring-boot-starter/badge.svg?color=blueviolet)\n![](https://img.shields.io/:license-Apache2-orange.svg)\n[![Style check](https://github.com/lzhpo/logger-spring-boot-starter/actions/workflows/style-check.yml/badge.svg)](https://github.com/lzhpo/logger-spring-boot-starter/actions/workflows/style-check.yml)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/b7b948873ebf40b4be396fe7f0483a97)](https://app.codacy.com/gh/lzhpo/logger-spring-boot-starter/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n\n## 开源地址\n\n- GitHub：[https://github.com/lzhpo/logger-spring-boot-starter](https://github.com/lzhpo/logger-spring-boot-starter)\n- Gitee：[https://gitee.com/lzhpo/logger-spring-boot-starter](https://gitee.com/lzhpo/logger-spring-boot-starter)\n\n## 前言\n\n\u003e 操作日志在一个系统中占据着举足轻重的位置，记录操作日志的方式也五花八门，但应讲究可读性、复杂场景支持、业务代码解耦，不侵入业务代码，以保持我们业务代码的整洁。\n\n## 如何使用？\n\n*logger同时支持SpringBoot2和SpringBoot3*\n\n### 1.导入依赖\n\n\u003e 依赖已发布至Maven中央仓库，可直接引入依赖。\n\n- Maven：\n\n  ```xml\n  \u003cdependency\u003e\n    \u003cgroupId\u003ecom.lzhpo\u003c/groupId\u003e\n    \u003cartifactId\u003elogger-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e${latest-version}\u003c/version\u003e\n  \u003c/dependency\u003e\n  ```\n- Gradle:\n  ```groovy\n  implementation 'com.lzhpo:logger-spring-boot-starter:${latest-version}'\n  ```\n\n### 2.使用注解\n\n#### 2.1 `@Logger`注解\n\n_`@Logger` 注解已经支持在 IDEA 中自动 SpringEL 表达式高亮并且自动提示。_\n\n`@Logger`注解解释：\n- condition: 生成日志的条件，非必需，true 或 false，支持 SpringEL 表达式。\n  ```java\n  // 获取返回结果中的成功标志\n  condition = \"#result.getSuccess()\"\n  ```\n- message: 日志内容，必需，支持 SpringEL 表达式。\n  ```java\n  // 从数据库中查询用户名称和产品名称，结果示例：小刘使用支付宝下单了ABC产品\n  message = \"#findUserName(#request.getUserId()) + '使用' + #request.getPaymentType() + '下单了' + #findProductName(#request.getProductId()) + '产品'\"\n  \n  // 从数据库中查询用户名称和地址，结果示例：小刘将地址从Jiangxi修改为Guangzhou\n  message = \"#findUserName(#request.getUserId()) + '将地址从' + #findOldAddress(#request.getOrderId()) + '修改为' + #findNewAddress(#request.getAddressId())\"\n  ```\n- operatorId: 日志关联的操作人，非必需，支持 SpringEL 表达式。也可以实现`OperatorAware`接口的`getCurrentOperatorId`方法进行获取。 如果既在注解传入了operatorId，又实现了`OperatorAware`接口，则优先取注解中的。\n  ```java\n  // 从数据库中查询用户名称\n  operatorId = \"#findUserName(#request.getUserId())\"\n  ```\n- businessId: 日志关联的业务编号，非必需，支持 SpringEL 表达式。\n  ```java\n  businessId = \"#getBusinessId(#result.getOrderId())\"\n  ```\n- category: 日志关联的类型，非必需，支持 SpringEL 表达式。\n  ```java\n  // 可以使用纯字符串或SpringEL表达式，自由发挥\n  category = \"'Operation Log'\"\n  ```\n- tag: 日志关联的标签，非必需，支持 SpringEL 表达式。\n  ```java\n  // 可以使用纯字符串或SpringEL表达式，自由发挥\n  tag = \"'Create Order'\"\n  ```\n- prelude: 日志在业务代码执行前解析还是在执行后解析（true：执行前解析；false：执行后解析），非必须，默认为 false。若为 true，在注解中的表达式无法使用 result 变量。\n- returning: 是否需要返回业务代码执行结果，非必须，默认为true。\n- additional: 日志额外的信息，非必需，可自由发挥，支持 SpringEL 表达式。\n  ```java\n  // 从数据库中查询用户名称和会员等级\n  additional = \"#findUserName(#request.getUserId()) + '等级是' + #findUserVip(#request.getUserId()) + '，请求日期' + T(java.time.LocalDateTime).now()\"\n  ```\n\n简单示例演示：\n\n_详细示例可看Junit测试用例_\n\n```java\n@PostMapping(\"/orders\")\n@Logger(\n    condition = \"#result.getSuccess()\",\n    category = \"'Operation Log'\",\n    tag = \"'Create Order'\",\n    businessId = \"#getBusinessId(#result.getOrderId())\",\n    operatorId = \"#findUserName(#request.getUserId())\",\n    message = \"#findUserName(#request.getUserId()) + '使用' + #request.getPaymentType() + '下单了' + #findProductName(#request.getProductId()) + '产品'\",\n    additional = \"#findUserName(#request.getUserId()) + '等级是' + #findUserVip(#request.getUserId()) + '，请求日期' + T(java.time.LocalDateTime).now()\"\n)\npublic CreateOrderResponse createOrder(@RequestBody CreateOrderRequest request) {\n    // ...\n}\n```\n\n#### 2.2 自定义函数注解\n\n- `@LoggerComponent`: 用于标识一个组件是属于 logger 的。\n- `@LoggerFunction`: 用于支持在 `@Logger` 注解中自定义函数，需要注意的是方法必须是 static，函数名默认取 `@LoggerFunction` 注解下的方法名，函数名支持自定义，例如：`@LoggerFunction(\"findUserName\")`\n\n简单示例演示：\n```java\n@LoggerComponent\npublic class OrderRegisterFunction {\n\n  @LoggerFunction\n  public static String findProductName(String productId) {\n    // ...\n  }\n}\n```\n\n#### 2.3 异步监听日志事件\n\n日志解析完毕之后会发布一个 `LoggerEvent` 事件，可以自定义 Listener 进行处理。\n\n例如：\n```java\n@Slf4j\n@Component\npublic class LoggerEventListener {\n\n    @Async\n    @EventListener\n    public void process(LoggerEvent event) {\n        log.info(\"Received LoggerEvent: {}\", event);\n        log.info(event.getMessage());\n    }\n}\n```\n\n`LoggerEvent` 字段解释:\n- logId: 日志编号。\n- message: 日志内容。\n- operatorId: 操作人编号。\n- businessId: 业务编号。\n- category: 日志分类。\n- tag: 日志标签。\n- additional: 日志额外信息。\n- createTime: 日志创建时间。\n- takeTime: 业务方法耗时，单位：毫秒。\n- result: 业务方法执行结果。\n- success: 业务方法是否执行成功。\n- errors: 业务方法执行期间发生的异常。\n- diffResults: 对象diff的结果。\n\n#### 2.4 对象 diff\n\n对象 diff 的意思就是给两个对象，找出它们的区别。\n\n示例：\n```java\n// 相同对象diff\n@Logger(message = \"#DIFF(#oldUser, #newUser)\")\npublic void userDiff(User oldUser, User newUser) {\n    // NOP\n}\n\n// 不同对象diff\n@Logger(message = \"#DIFF(#admin, #user)\")\npublic void adminUserDiff(Admin admin, User user) {\n  // NOP\n}\n```\n\n其中，`DIFF` 是内置的函数，它会返回字符串形式的 diff 格式化结果，如有多个 diff 结果可设置指定的字符进行分隔。\n`DIFF` 的结果会放在 `LoggerEvent` 的 `diffResults` 字段，如有特殊需求，可在 `LoggerEvent` 的监听器里面进行处理。\n\n支持自定义模板和分隔符：\n```yml\nlogger:\n  diff:\n    delimiter: \", \"\n    template: \"[{filedName}] has been updated from [{oldValue}] to [{newValue}]\"\n```\n\n同时 diff 也支持排除指定对象或字段，或者设置 diff 字段的标题。\n\n```java\n// 排除此对象diff\n@LoggerDiffObject(disabled = true)\npublic class UserWithDisabledObject {\n\n    private String username;\n    \n    private String email;\n}\n```\n\n```java\npublic class UserWithDisabledField {\n\n    // 排除此字段diff\n    @LoggerDiffField(disabled = true)\n    private String username;\n    \n    private String email;\n}\n```\n\n```java\npublic class UserWithTitle {\n\n    // 设置字段的标题\n    @LoggerDiffField(title = \"用户名称\")\n    private String username;\n\n    // 设置字段的标题\n    @LoggerDiffField(title = \"用户年龄\")\n    private Integer age;\n}\n```\n\n#### 2.5 关于`@Logger`注解在IDEA设置SpringEL的提示\n\n`@Logger` 注解中的属性已经支持在 IDEA 中自动高亮显示，并且有 SpringEL 的提示，无需手动设置。\n\n![](docs/images/SpringEL-IDEA-7.png)\n\n![](docs/images/SpringEL-IDEA-6.png)\n\n## 微信公众号\n\n\u003cimg src=\"./docs/images/WeChat-MP.png\" width=\"453\" height=\"150\" alt=\"会打篮球的程序猿\"\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flzhpo%2Flogger-spring-boot-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flzhpo%2Flogger-spring-boot-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flzhpo%2Flogger-spring-boot-starter/lists"}