在Java开发中,我们经常会遇到一个棘手的问题:记录用户的操作行为。
某些操作是相对简单的,我们可以逐条记录。但是某些操作行为却很难记录,例如编辑操作。在某一次操作中,用户可能编辑了对象A的几个属性,而下一次操作中用户可能编辑了对象B的几个属性。如果我们针对对象A、对象B的属性变化分别进行记录,则整个操作十分复杂。而且,会与业务操作高度耦合。
而今天我们介绍的是一个叫ObjectLogger的系统,它是一个强大且易用的Java对象日志记录系统,能够分析任何对象的属性变化,实现对象变化的记录与查询。
因此,它可以应用在用户操作日志记录、对象属性变更记录等诸多场景中。简单易用,实为利器。
基于它,我们可以很方便地实现下面的效果。
该系统为github开源项目,地址为:https://github.com/yeecode/ObjectLogger
下面我们简单介绍下该系统。基于它,我们可以非常方便地搭建一套日志记录系统。
该系统具有以下特点:
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}
上述命令中的用户配置项说明如下:
启动jar包后,系统默认的服务地址为:
http://127.0.0.1:8080/ObjectLogger/
访问上述地址可以看到下面的欢迎界面:
至此,ObjectLogger系统已经搭建结束,可以接受业务系统的日志写入和查询操作。
该部分讲解如何配置业务系统来将业务系统中的对象变化记录到ObjectLogger中。
3.1 引入依赖包
在pom中增加下面的依赖:
<dependency>
<groupId>com.github.yeecode.objectLogger</groupId>
<artifactId>ObjectLoggerClient</artifactId>
<version>{最新版本}</version>
</dependency>
3.2 添加对ObjectLoggerClient中bean的自动注入
3.2.1 对于SpringBoot应用
在SpringBoot的启动类前添加@ComponentScan注解,并在basePackages中增加ObjectLoggerClient的包地址:com.github.yeecode.objectLoggerClient,如:
@SpringBootApplication
@ComponentScan(basePackages={"{your_beans_root}","com.github.yeecode.objectLogger"})
public class MyBootAppApplication {
public static void main(String[] args) {
// 省略其他代码
}
}
3.2.2 对于Spring应用
在applicationContext.xml增加对ObjectLoggerClient包地址的扫描:
<context:component-scan base-package="com.github.yeecode.objectLoggerClient">
</context:component-scan>
3.3 完成配置
在application.properties中增加:
object.logger.add.log.api=http://{ObjectLogger_address}/ObjectLogger/log/add
object.logger.appName={your_app_name}
object.logger.autoLog=true
至此,业务系统的配置完成。已经实现了和ObjectLogger的Server端的对接。
系统运行后,可以通过/ObjectLogger/log/query查询系统中记录的日志,并通过传入参数对日志进行过滤。
通过这里,我们可以查询下一步中写入的日志。
业务系统在任何需要进行日志记录的类中引入LogClient。例如:
@Autowired
private LogClient logClient;
5.1 简单使用
直接将对象的零个、一个、多个属性变化放入actionItemModelList中发出即可。actionItemModelList置为null则表示此次对象无需要记录的属性变动。例如,业务应用中调用:
logClient.sendLogForItems("TaskModel",5,"actor name","addTask","add Task","via web page","some comments",null);
在ObjectLogger中使用如下查询条件:
http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=5
查询到日志:
{
"respMsg": "成功",
"respData": [
{
"id": 16,
"appName": "myBootApp",
"objectName": "TaskModel",
"objectId": 5,
"actor": "actor name",
"action": "addTask",
"actionName": "add Task",
"extraWords": "via web page",
"comment": "some comments",
"actionTime": "2019-04-10T10:56:15.000+0000",
"actionItemModelList": []
}
],
"respCode": "1000"
}
5.2 对象变动自动记录
该功能可以自动完成新老对象的对比,并根据对比结果,将多个属性变动一起写入日志系统中。使用时,要确保传入的新老对象属于同一个类。
例如,业务系统这样调用:
TaskModel oldTaskModel = new TaskModel();
oldTaskModel.setId(9);
oldTaskModel.setTaskName("oldName");
oldTaskModel.setUserId(3);
oldTaskModel.setDescription("\t<p>the first line</p>\n" +
"\t<p>the second line</p>\n" +
"\t<p>the 3th line</p>");
TaskModel newTaskModel = new TaskModel();
newTaskModel.setId(9);
newTaskModel.setTaskName("newName");
newTaskModel.setUserId(5);
newTaskModel.setDescription("\t<p>the first line</p>\n" +
"\t<p>the second line</p>\n" +
"\t<p>the last line</p>");
logClient.sendLogForObject(9,"actor name","editTask","edit Task","via app",
"some comments",oldTaskModel,newTaskModel);
则我们可以使用下面查询条件:
http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=9
查询到如下结果:
{
"respMsg": "成功",
"respData": [
{
"id": 15,
"appName": "myBootApp",
"objectName": "TaskModel",
"objectId": 9,
"actor": "actor name",
"action": "editTask",
"actionName": "edit Task",
"extraWords": "via app",
"comment": "some comments",
"actionTime": "2019-04-10T10:56:17.000+0000",
"actionItemModelList": [
{
"id": 18,
"actionId": 15,
"attributeType": "NORMAL",
"attribute": "taskName",
"attributeName": "TASK",
"oldValue": "oldName",
"newValue": "newName",
"diffValue": null
},
{
"id": 19,
"actionId": 15,
"attributeType": "USERID",
"attribute": "userId",
"attributeName": "USER",
"oldValue": "USER:3",
"newValue": "USER:5",
"diffValue": "diffValue"
},
{
"id": 20,
"actionId": 15,
"attributeType": "TEXT",
"attribute": "description",
"attributeName": "DESCRIPTION",
"oldValue": "\"\\t<p>the first line</p>\\n\\t<p>the second line</p>\\n\\t<p>the 3th line</p>\"",
"newValue": "\"\\t<p>the first line</p>\\n\\t<p>the second line</p>\\n\\t<p>the last line</p>\"",
"diffValue": "第6行变化:<br/> -:<del> the 3th line </del> <br/> +:<u> the last line </u> <br/>"
}
]
}
],
"respCode": "1000"
}
有些对象的属性的变动不需要进行日志记录,例如updateTime、hashCode等。ObjectLogger支持对对象的属性进行过滤,只追踪我们感兴趣的属性。
并且,对于每个属性我们可以更改其记录到ObjectLogger系统中的具体方式,例如修改命名等。
要想启用这个功能,首先将配置中的object.logger.autoLog改为false。
object.logger.autoLog=false
然后在需要进行变化日志记录的属性上增加@LogTag注解。凡是没有增加该注解的属性在日志记录时会被自动跳过。
例如,注解配置如下则id字段的变动将被忽略。
private Integer id;
@LogTag(name = "TaskName")
private String taskName;
@LogTag(name = "UserId", extendedType = "userIdType")
private int userId;
@LogTag(name = "Description", builtinType = BuiltinTypeHandler.TEXT)
private String description;
该注解属性介绍如下:
很多情况下,用户希望能够自主决定某些对象属性的处理方式。例如,对于例子中Task对象的userId属性,用户可能想将其转化为姓名后存入日志系统,从而使得日志系统与userId完全解耦。
ObjectLogger完全支持这种情况,可以让用户自主决定某些属性的日志记录方式。要想实现这种功能,首先在需要进行扩展处理的属性上为@LogTag的extendedType属性赋予一个字符串值。例如:
@LogTag(name = "UserId", extendedType = "userIdType")
private int userId;
然后在业务系统中声明一个Bean继承BaseExtendedTypeHandler,作为自由扩展的钩子。代码如下:
@Service
public class ExtendedTypeHandler implements BaseExtendedTypeHandler {
@Override
public BaseActionItemModel handleAttributeChange(String attributeName, String logTagName, Object oldValue, Object newValue) {
return null;
}
}
接下来,当ObjectLogger处理到该属性时,会将该属性的相关信息传入到扩展Bean的handleAttributeChange方法中,然后用户可以自行处理。传入的四个参数解释如下:
例如我们可以采用如下的方式处理userIdType属性:
public BaseActionItemModel handleAttributeChange(String extendedType, String attributeName, String logTagName, Object oldValue, Object newValue) {
BaseActionItemModel baseActionItemModel = new BaseActionItemModel();
if (extendedType.equals("userIdType")) {
baseActionItemModel.setOldValue("USER_" + oldValue);
baseActionItemModel.setNewValue("USER_" + newValue);
baseActionItemModel.setDiffValue(oldValue + "->" + newValue);
}
return baseActionItemModel;
}
怎么样,是不是ObjectLogger https://github.com/yeecode/ObjectLogger 的存在极大地方便了我们的日志记录操作。
欢迎关注我们,了解Java架构师原创干货!
--End--
▼往期精彩文章▼
欢迎关注我们,不错过每期的原创干货!
留言与评论(共有 0 条评论) |