架构师必看!操作日志系统搭建秘技

系统
在Java开发中,我们经常会遇到一个棘手的问题:记录用户的操作行为。

 在Java开发中,我们经常会遇到一个棘手的问题:记录用户的操作行为。

某些操作是相对简单的,我们可以逐条记录。但是某些操作行为却很难记录,例如编辑操作。在某一次操作中,用户可能编辑了对象A的几个属性,而下一次操作中用户可能编辑了对象B的几个属性。如果我们针对对象A、对象B的属性变化分别进行记录,则整个操作十分复杂。而且,会与业务操作高度耦合。

[[269578]]

而今天我们介绍的是一个叫ObjectLogger的系统,它是一个强大且易用的Java对象日志记录系统,能够分析任何对象的属性变化,实现对象变化的记录与查询。

因此,它可以应用在用户操作日志记录、对象属性变更记录等诸多场景中。简单易用,实为利器。

基于它,我们可以很方便地实现下面的效果。

 

架构师必看!操作日志系统搭建秘技

该系统为github开源项目,地址为:https://github.com/yeecode/ObjectLogger

下面我们简单介绍下该系统。基于它,我们可以非常方便地搭建一套日志记录系统。

1 系统特点

该系统具有以下特点:

  • 一站整合:系统支持日志的记录与查询,开发者只需再开发前端界面即可使用。
  • 完全独立:与业务系统无耦合,可插拔使用,不影响主业务流程。
  • 应用共享:系统可以同时供多个业务系统使用,互不影响。
  • 简单易用:服务端直接jar包启动;业务系统有官方Maven插件支持。
  • 自动解析:能自动解析对象的属性变化,并支持富文本的前后对比。
  • 便于扩展:支持自定义对象变动说明、属性变动说明。支持更多对象属性类型的扩展。

2 快速上手

2.1 创建数据库

使用该项目的/server/database/init_data_table.sql文件初始化两个数据表。

2.2 启动Server

下载该项目下***的Server服务jar包,地址为/server/target/ObjectLogger-*.jar。

启动下载的jar包。

java -jar ObjectLogger-*.jar --spring.datasource.driver-class-name={db_driver} --spring.datasource.url=jdbc:{db}://{db_address}/{db_name} --spring.datasource.username={db_username} --spring.datasource.password={db_password}

上述命令中的用户配置项说明如下:

  • db_driver:数据库驱动。如果使用MySql数据库则为com.mysql.jdbc.Driver;如果使用SqlServer数据库则为com.microsoft.sqlserver.jdbc.SQLServerDriver。
  • db:数据库类型。如果使用MySql数据库则为mysql;如果使用SqlServer数据库则为sqlserver。
  • db_address:数据库连接地址。如果数据库在本机则为127.0.0.1。
  • db_name:数据库名,该数据库中需包含上一步初始化的两个数据表。
  • db_username:数据库登录用户名。
  • db_password:数据库登录密码。

启动jar包后,系统默认的服务地址为:

http://127.0.0.1:8080/ObjectLogger/

访问上述地址可以看到下面的欢迎界面:

 

架构师必看!操作日志系统搭建秘技

 

至此,ObjectLogger系统已经搭建结束,可以接受业务系统的日志写入和查询操作。

3 业务系统接入

该部分讲解如何配置业务系统来将业务系统中的对象变化记录到ObjectLogger中。

3.1 引入依赖包

在pom中增加下面的依赖:

  1. <dependency> 
  2.  <groupId>com.github.yeecode.objectLogger</groupId> 
  3.  <artifactId>ObjectLoggerClient</artifactId> 
  4.  <version>{***版本}</version> 
  5. </dependency> 

3.2 添加对ObjectLoggerClient中bean的自动注入

3.2.1 对于SpringBoot应用

在SpringBoot的启动类前添加@ComponentScan注解,并在basePackages中增加ObjectLoggerClient的包地址:com.github.yeecode.objectLoggerClient,如:

  1. @SpringBootApplication 
  2. @ComponentScan(basePackages={"{your_beans_root}","com.github.yeecode.objectLogger"}) 
  3. public class MyBootAppApplication { 
  4. public static void main(String[] args) { 
  5.  // 省略其他代码 
  6.  } 

3.2.2 对于Spring应用

在applicationContext.xml增加对ObjectLoggerClient包地址的扫描:

  1. <context:component-scan base-package="com.github.yeecode.objectLoggerClient"
  2. </context:component-scan> 

3.3 完成配置

在application.properties中增加:

  1. object.logger.add.log.api=http://{ObjectLogger_address}/ObjectLogger/log/add 
  2. object.logger.appName={your_app_name} 
  3. object.logger.autoLog=true 
  • ObjectLogger_address:属性指向上一步的ObjectLogger的部署地址,例如:127.0.0.1:8080
  • your_app_name:指当前业务系统的应用名。以便于区分日志来源,实现同时支持多个业务系统
  • object.logger.autoLog:是否对对象的所有属性进行变更日志记录

至此,业务系统的配置完成。已经实现了和ObjectLogger的Server端的对接。

4 日志查询

系统运行后,可以通过/ObjectLogger/log/query查询系统中记录的日志,并通过传入参数对日志进行过滤。

 

架构师必看!操作日志系统搭建秘技

 

 

通过这里,我们可以查询下一步中写入的日志。

5 日志写入

业务系统在任何需要进行日志记录的类中引入LogClient。例如:

  1. @Autowired 
  2. private LogClient logClient; 

5.1 简单使用

直接将对象的零个、一个、多个属性变化放入actionItemModelList中发出即可。actionItemModelList置为null则表示此次对象无需要记录的属性变动。例如,业务应用中调用:

  1. logClient.sendLogForItems("TaskModel",5,"actor name","addTask","add Task","via web page","some comments",null); 

在ObjectLogger中使用如下查询条件:

  1. http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=5 

查询到日志:

  1.  "respMsg""成功"
  2.  "respData": [ 
  3.  { 
  4.  "id": 16, 
  5.  "appName""myBootApp"
  6.  "objectName""TaskModel"
  7.  "objectId": 5, 
  8.  "actor""actor name"
  9.  "action""addTask"
  10.  "actionName""add Task"
  11.  "extraWords""via web page"
  12.  "comment""some comments"
  13.  "actionTime""2019-04-10T10:56:15.000+0000"
  14.  "actionItemModelList": [] 
  15.  } 
  16.  ], 
  17.  "respCode""1000" 

5.2 对象变动自动记录

该功能可以自动完成新老对象的对比,并根据对比结果,将多个属性变动一起写入日志系统中。使用时,要确保传入的新老对象属于同一个类。

例如,业务系统这样调用:

  1. TaskModel oldTaskModel = new TaskModel(); 
  2. oldTaskModel.setId(9); 
  3. oldTaskModel.setTaskName("oldName"); 
  4. oldTaskModel.setUserId(3); 
  5. oldTaskModel.setDescription("   <p>the first line</p> 
  6. " + 
  7.  "  <p>the second line</p> 
  8. " + 
  9.  "  <p>the 3th line</p>"); 
  10. TaskModel newTaskModel = new TaskModel(); 
  11. newTaskModel.setId(9); 
  12. newTaskModel.setTaskName("newName"); 
  13. newTaskModel.setUserId(5); 
  14. newTaskModel.setDescription("   <p>the first line</p> 
  15. " + 
  16.  "  <p>the second line</p> 
  17. " + 
  18.  "  <p>the last line</p>"); 
  19. logClient.sendLogForObject(9,"actor name","editTask","edit Task","via app"
  20. "some comments",oldTaskModel,newTaskModel); 

则我们可以使用下面查询条件:

  1. http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=9 

查询到如下结果:

  1.  "respMsg""成功"
  2.  "respData": [ 
  3.  { 
  4.  "id": 15, 
  5.  "appName""myBootApp"
  6.  "objectName""TaskModel"
  7.  "objectId": 9, 
  8.  "actor""actor name"
  9.  "action""editTask"
  10.  "actionName""edit Task"
  11.  "extraWords""via app"
  12.  "comment""some comments"
  13.  "actionTime""2019-04-10T10:56:17.000+0000"
  14.  "actionItemModelList": [ 
  15.  { 
  16.  "id": 18, 
  17.  "actionId": 15, 
  18.  "attributeType""NORMAL"
  19.  "attribute""taskName"
  20.  "attributeName""TASK"
  21.  "oldValue""oldName"
  22.  "newValue""newName"
  23.  "diffValue"null 
  24.  }, 
  25.  { 
  26.  "id": 19, 
  27.  "actionId": 15, 
  28.  "attributeType""USERID"
  29.  "attribute""userId"
  30.  "attributeName""USER"
  31.  "oldValue""USER:3"
  32.  "newValue""USER:5"
  33.  "diffValue""diffValue" 
  34.  }, 
  35.  { 
  36.  "id": 20, 
  37.  "actionId": 15, 
  38.  "attributeType""TEXT"
  39.  "attribute""description"
  40.  "attributeName""DESCRIPTION"
  41.  "oldValue"""\t<p>the first line</p>\n\t<p>the second line</p>\n\t<p>the 3th line</p>""
  42.  "newValue"""\t<p>the first line</p>\n\t<p>the second line</p>\n\t<p>the last line</p>""
  43.  "diffValue""第6行变化:<br/> -:<del> the 3th line </del> <br/> +:<u> the last line </u> <br/>" 
  44.  } 
  45.  ] 
  46.  } 
  47.  ], 
  48.  "respCode""1000" 

6 对象属性过滤

有些对象的属性的变动不需要进行日志记录,例如updateTime、hashCode等。ObjectLogger支持对对象的属性进行过滤,只追踪我们感兴趣的属性。

并且,对于每个属性我们可以更改其记录到ObjectLogger系统中的具体方式,例如修改命名等。

要想启用这个功能,首先将配置中的object.logger.autoLog改为false。

  1. object.logger.autoLog=false 

然后在需要进行变化日志记录的属性上增加@LogTag注解。凡是没有增加该注解的属性在日志记录时会被自动跳过。

例如,注解配置如下则id字段的变动将被忽略。

  1. private Integer id; 
  2. @LogTag(name = "TaskName"
  3. private String taskName; 
  4. @LogTag(name = "UserId", extendedType = "userIdType"
  5. private int userId; 
  6. @LogTag(name = "Description", builtinType = BuiltinTypeHandler.TEXT) 
  7. private String description; 

该注解属性介绍如下:

  • name:必填,对应写入日志后的attributeName值。
  • builtinType:ObjectLogger的内置类型,为BuiltinTypeHandler的值。默认为BuiltinTypeHandler.NORMAL。
  • BuiltinTypeHandler.NORMAL:记录属性的新值和旧值,对比值为null
  • BuiltinTypeHandler.TEXT: 用户富文本对比。记录属性值的新值和旧值,并将新旧值转化为纯文本后逐行对比差异,对比值中记录差异
  • extendedType:扩展属性类型。使用ObjcetLogger时,用户可以扩展某些字段的处理方式。

7 属性处理扩展

很多情况下,用户希望能够自主决定某些对象属性的处理方式。例如,对于例子中Task对象的userId属性,用户可能想将其转化为姓名后存入日志系统,从而使得日志系统与userId完全解耦。

ObjectLogger完全支持这种情况,可以让用户自主决定某些属性的日志记录方式。要想实现这种功能,首先在需要进行扩展处理的属性上为@LogTag的extendedType属性赋予一个字符串值。例如:

  1. @LogTag(name = "UserId", extendedType = "userIdType"
  2.  private int userId; 

然后在业务系统中声明一个Bean继承BaseExtendedTypeHandler,作为自由扩展的钩子。代码如下:

  1. @Service 
  2. public class ExtendedTypeHandler implements BaseExtendedTypeHandler { 
  3.  @Override 
  4.  public BaseActionItemModel handleAttributeChange(String attributeName, String logTagName, Object oldValue, Object newValue) { 
  5.  return null
  6.  } 

接下来,当ObjectLogger处理到该属性时,会将该属性的相关信息传入到扩展Bean的handleAttributeChange方法中,然后用户可以自行处理。传入的四个参数解释如下:

  • extendedType:扩展类型值,即@LogTag注解的extendedType值。本示例中为userIdType。
  • attributeName:属性名。本示例中为userId。
  • logTagName:@LogTag注解的name值,可能为null。本示例中为UserId。
  • oldValue:该属性的旧值。
  • newValue:该属性的新值。

例如我们可以采用如下的方式处理userIdType属性:

  1. public BaseActionItemModel handleAttributeChange(String extendedType, String attributeName, String logTagName, Object oldValue, Object newValue) { 
  2.  BaseActionItemModel baseActionItemModel = new BaseActionItemModel(); 
  3.  if (extendedType.equals("userIdType")) { 
  4.  baseActionItemModel.setOldValue("USER_" + oldValue); 
  5.  baseActionItemModel.setNewValue("USER_" + newValue); 
  6.  baseActionItemModel.setDiffValue(oldValue + "->" + newValue); 
  7.  } 
  8.  return baseActionItemModel; 

8 总结

怎么样,是不是ObjectLogger https://github.com/yeecode/ObjectLogger 的存在极大地方便了我们的日志记录操作。

责任编辑:武晓燕 来源: 今日头条
相关推荐

2015-12-09 15:16:03

架构师京东架构

2018-10-11 10:55:44

分布式文件系统HDFS

2012-12-17 17:38:37

System CentWindows SerHyper-V

2011-10-31 09:22:07

系统架构

2011-10-21 09:04:57

系统架构师

2011-10-19 09:20:44

2022-11-02 09:53:54

架构核酸

2011-11-01 09:02:26

系统架构师

2011-10-24 09:26:42

系统架构师

2011-10-20 09:06:36

系统架构师

2009-12-18 10:22:50

Ray Ozzie架构师

2020-08-24 08:50:12

架构师TL技术

2017-11-27 14:12:34

大数据Hadoop数据分析

2011-10-18 09:25:04

系统架构师

2011-10-27 09:08:59

系统架构师

2011-11-02 09:01:30

系统架构师

2018-02-08 17:27:23

大数据HadoopGoogle

2018-07-03 15:46:24

Java架构师源码

2018-09-17 05:00:59

架构系统练级

2009-12-29 11:02:20

架构师艺术气质
点赞
收藏

51CTO技术栈公众号