跳转到内容

操作日志

背景

通常,我们的业务系统需要审计功能,需要记录业务数据在某时某刻被谁做了某个变更,以便后续出现争议时的溯源。为此, trantor 在机制侧提供此功能,以便业务应用快速接入。

使用

操作日志分为两类:

  • 系统内置日志: 用户在统一工作台上进行数据创建/更新删除时的记录。
  • 用户自定义日志: 通过 Trantor 提供的 SDK 写入的日志记录(需满足自定义日志规范)。

操作日志与模型强关联,日志记录如下关键信息:

  • 操作人
  • 操作人 IP
  • 操作时间
  • 操作动作
  • 操作模型
  • 操作模型记录 id

其中,操作动作展示优先级: i18n text > action label > functionKey, 若没有配置 i18n 或 action label,默认展示为 functionKey。

由于操作日志记录的是模型数据变更,故操作日志只会在模型详情/编辑页右上角展开查看, 效果如下图所示:

目前,业务业务应用主要使用系统内置日志,下面以内置日志开展介绍。

工作流程

操作日志依赖 RocketMQ 中间件,用户通过 Datastore SDK 进行模型记录创建/更新/删除时,Datastore 判断此模型是否开启消息订阅。若开启,发送消息至 RocketMQ, Metastore 作为消息消费者,消费消息,记录操作日志。具体流程如下:

  1. 用户在统一工作台触发模型变更操作时(携带操作人、操作人 IP、操作动作, requestId 等信息),走 http 发送日志至 metastore。
  2. Metastore 缓存操作人等信息的日志。
  3. Datastore 感知模型数据变更,发送模型数据变更消息。
  4. Metastore 监听到模型数据变更消息后,通过消息携带的 requestId 关联,获取之前缓存的操作人、操作动作等信息,生成一条新的日志记录,持久化至数据库中。

配置

1. Datastore 开启消息发送

配置环境变量:

SUBSCRIBE_VERSION: V2
MQ_SERVER_ADDRESS: <您的 RocketMQ nameserver 地址>
SUBSCRIBE: true

确保模型变更时的消息发送。

2. Metastore 开启消息消费

配置环境变量:

DS_SUBSCRIBE: true
TRANTOR_EVENT_ENABLED: true

确保运行态 metastore 服务作为消费者顺利消费。

3. 交付控制台(console)开启模型日志订阅

交付控制台->运行环境->配置->配置中心 内,在 模型日志订阅 列表中选择要订阅的日志模型(持久化模型, 即: BO), 如图所示:

4. 统一工作台(workspace)开启日志开关

配置环境变量:

WEBNEST_FEATURE: ENABLE_LOG

控制操作日志前端是否展示。

注: 前端是否展示与后端日志是否存储无关, 即使前端展示开关未开启, 后端也会记录日志。

日志归档

针对历史日志有两种策略:1. 归档 2. 清理。日志默认保留 15 天,可通过配置运行态 metastore 环境变量 TRANTOR_EVENT_MAX_RESERVED 调整,默认为清理策略,可通过 交付控制台->运行环境->配置中心的历史日志处理选择归档,如下图所示:

归档的日志默认保存在阿里云 OSS 上,若选择日志归档,请务必配置公有桶,公有桶在运行态 metastore 环境变量: PUBLIC_BUCKET 指定。

日志列表

日志列表包含三部分:

  • 系统日志
  • 业务日志
  • 归档日志

在【交付控制台】模块菜单配置, 请选择最顶层应用进行配置,有交付物存在时,在交付物的模块配置菜单;交付物不存在时,选择最顶层的应用模块进行配置。event_ModelChangeEvent_BusinessLogList 为业务日志,event_ModelChangeEvent_SystemLogList 为系统日志,event_ArchiveLog_ArchiveLogList 为归档日志。如下图所示:

模块菜单配置完成后,请执行发布计划,将模块菜单发布至运行态。

若在配置日志菜单时选不到对应菜单路由,请在研发态 ms 数据库执行如下 SQL:

INSERT INTO `meta_store_management__versioned_view` (`id`, `name`, `source`, `menuView`, `key`, `bizAppKey`, `relatedResources`, `isDefault`, `_version`, `createdAt`, `type`, `compiledControllerHash`, `updatedAt`, `compiledControllerCode`, `controllerCode`, `code`, `modelKey`, `resourceKey`, `version`, `projectId`, `tenantId`, `productVersion`, `overrideAppKey`, `digester`, `modificationType`, `desc`, `originalKey`, `isDeleted`, `deletedAt`, `FromTenant`, `FromProject`, `FromModel`, `DependencyProductVersion`, `UpdatedBy`, `FromModule`, `CreatedBy`)
VALUES
('d36ef6fff8cfded5a8c456c8ebaf1421', NULL, 'Custom', 1, 'base_event_ArchiveLog_ArchiveLogList', NULL, '{\"keyResources\":{\"LogicController\":[],\"LogicFunction\":[],\"TreeDataSource\":[],\"View\":[],\"LogicFlow\":[]},\"models\":[{\"key\":\"event_ArchiveLog\",\"fields\":{\"archiveType\":{\"name\":\"archiveType\"},\"logDate\":{\"name\":\"logDate\"},\"createdAt\":{\"name\":\"createdAt\"},\"downloadUrl\":{\"name\":\"downloadUrl\"}}}]}', 0, 1, '2022-03-07 09:31:13', 'List', NULL, '2022-03-07 09:31:13', NULL, NULL, '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<View title=\"#{i18n.get(\'归档日志\').d(\'归档日志\')}\" forModel=\"event_ArchiveLog\" menuView=\"true\" type=\"List\" version=\"2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://trantor-docs-dev.app.terminus.io/static/v0.16.x/schema/base.xsd\">\n <Table model=\"event_ArchiveLog\" dataCondition=\"\">\n <Fields>\n <Field name=\"archiveType\"/>\n <Field name=\"logDate\">\n <RenderType>\n <Date format=\"YYYY-MM-DD HH:mm:ss\"/>\n </RenderType>\n </Field>\n <Field name=\"createdAt\">\n <RenderType>\n <Date format=\"YYYY-MM-DD HH:mm:ss\"/>\n </RenderType>\n </Field>\n <Field name=\"downloadUrl\"/>\n </Fields>\n </Table>\n</View>', 'event_ArchiveLog', 'event_ArchiveLog_ArchiveLogList', '1.0.0', NULL, 1, '1.0.0', NULL, '1d62a93f1cb5ff83468144d40d7eb68c', 'Static', NULL, 'ArchiveLogList', 0, 0, 1, NULL, NULL, '76d52c6373fe7da00535ed4f0ea125bd', NULL, '0867a2f15dca83f635b63da83df334da', NULL);
INSERT INTO `meta_store_management__versioned_view` (`id`, `name`, `source`, `menuView`, `key`, `bizAppKey`, `relatedResources`, `isDefault`, `_version`, `createdAt`, `type`, `compiledControllerHash`, `updatedAt`, `compiledControllerCode`, `controllerCode`, `code`, `modelKey`, `resourceKey`, `version`, `projectId`, `tenantId`, `productVersion`, `overrideAppKey`, `digester`, `modificationType`, `desc`, `originalKey`, `isDeleted`, `deletedAt`, `FromTenant`, `FromProject`, `FromModel`, `DependencyProductVersion`, `UpdatedBy`, `FromModule`, `CreatedBy`)
VALUES
('84fd88b7a7d442e82d26b3b7972a4f79', NULL, 'Custom', 1, 'base_event_ModelChangeEvent_BusinessLogList', NULL, '{\"keyResources\":{\"LogicController\":[],\"LogicFunction\":[],\"TreeDataSource\":[],\"View\":[\"event_ModelChangeEvent_BusinessLogDetail\"],\"LogicFlow\":[]},\"models\":[{\"key\":\"event_ModelChangeEvent\",\"fields\":{\"recordId\":{\"name\":\"recordId\"},\"modelName\":{\"name\":\"modelName\"},\"ip\":{\"name\":\"ip\"},\"menuName\":{\"name\":\"menuName\"},\"id\":{\"name\":\"id\"},\"type\":{\"name\":\"type\"},\"operatorName\":{\"name\":\"operatorName\"},\"applicationName\":{\"name\":\"applicationName\"},\"timestamp\":{\"name\":\"timestamp\"}}}]}', 0, 1, '2022-03-07 09:31:15', 'List', NULL, '2022-03-07 09:31:15', NULL, NULL, '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<View title=\"#{i18n.get(\'企业日志\').d(\'企业日志\')}\" forModel=\"event_ModelChangeEvent\" menuView=\"true\" type=\"List\" version=\"2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://trantor-docs-dev.app.terminus.io/static/v0.16.x/schema/base.xsd\">\n <Table model=\"event_ModelChangeEvent\" dataCondition=\"eventCategory = \'Business\'\">\n <Search>\n <Fields>\n <Field name=\"operatorName\"/>\n <Field name=\"applicationName\"/>\n <Field name=\"menuName\"/>\n <Field name=\"type\"/>\n <Field name=\"modelName\"/>\n <Field name=\"recordId\"/>\n <Field name=\"timestamp\"/>\n </Fields>\n </Search>\n <Fields>\n <Field name=\"id\"/>\n <Field name=\"operatorName\"/>\n <Field name=\"applicationName\"/>\n <Field name=\"menuName\"/>\n <Field name=\"type\"/>\n <Field name=\"modelName\"/>\n <Field name=\"recordId\"/>\n <Field name=\"timestamp\">\n <RenderType>\n <Date format=\"YYYY-MM-DD HH:mm:ss\" />\n </RenderType>\n </Field>\n <Field name=\"ip\"/>\n </Fields>\n <RecordActions label=\"操作\">\n <Action label=\"Detail\" targetView=\"event_ModelChangeEvent_BusinessLogDetail\"/>\n </RecordActions>\n </Table>\n</View>', 'event_ModelChangeEvent', 'event_ModelChangeEvent_BusinessLogList', '1.0.0', NULL, 1, '1.0.0', NULL, '50fc2286c9caa536ebc806515cd3919b', 'Static', NULL, 'BusinessLogList', 0, 0, 1, NULL, NULL, '76d52c6373fe7da00535ed4f0ea125bd', NULL, '0867a2f15dca83f635b63da83df334da', NULL);
INSERT INTO `meta_store_management__versioned_view` (`id`, `name`, `source`, `menuView`, `key`, `bizAppKey`, `relatedResources`, `isDefault`, `_version`, `createdAt`, `type`, `compiledControllerHash`, `updatedAt`, `compiledControllerCode`, `controllerCode`, `code`, `modelKey`, `resourceKey`, `version`, `projectId`, `tenantId`, `productVersion`, `overrideAppKey`, `digester`, `modificationType`, `desc`, `originalKey`, `isDeleted`, `deletedAt`, `FromTenant`, `FromProject`, `FromModel`, `DependencyProductVersion`, `UpdatedBy`, `FromModule`, `CreatedBy`)
VALUES
('d36ef6fff8cfded5a8c456c8ebaf1420', NULL, 'Custom', 1, 'base_event_ModelChangeEvent_SystemLogList', NULL, '{\"keyResources\":{\"LogicController\":[],\"LogicFunction\":[],\"TreeDataSource\":[],\"View\":[],\"LogicFlow\":[]},\"models\":[{\"key\":\"event_ModelChangeEvent\",\"fields\":{\"eventEndPoint\":{\"name\":\"eventEndPoint\"},\"ip\":{\"name\":\"ip\"},\"id\":{\"name\":\"id\"},\"type\":{\"name\":\"type\"},\"operatorName\":{\"name\":\"operatorName\"},\"operator\":{\"name\":\"operator\"},\"timestamp\":{\"name\":\"timestamp\"}}}]}', 0, 1, '2022-03-07 09:31:13', 'List', NULL, '2022-03-07 09:31:13', NULL, NULL, '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<View title=\"#{i18n.get(\'用户日志\').d(\'用户日志\')}\" forModel=\"event_ModelChangeEvent\" menuView=\"true\" type=\"List\" version=\"2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"https://trantor-docs-dev.app.terminus.io/static/v0.16.x/schema/base.xsd\">\n <Table model=\"event_ModelChangeEvent\" dataCondition=\"eventCategory = \'System\'\">\n <Search>\n <Fields>\n <Field name=\"operatorName\"/>\n <Field name=\"type\"/>\n <Field name=\"eventEndPoint\"/>\n <Field name=\"timestamp\"/>\n </Fields>\n </Search>\n <Fields>\n <Field name=\"id\"/>\n <Field name=\"operator\"/>\n <Field name=\"operatorName\"/>\n <Field name=\"type\"/>\n <Field name=\"timestamp\">\n <RenderType>\n <Date format=\"YYYY-MM-DD HH:mm:ss\" />\n </RenderType>\n </Field>\n <Field name=\"eventEndPoint\"/>\n <Field name=\"ip\"/>\n </Fields>\n </Table>\n</View>', 'event_ModelChangeEvent', 'event_ModelChangeEvent_SystemLogList', '1.0.0', NULL, 1, '1.0.0', NULL, '3d62a93f1cb5ff83468144d40d7eb68c', 'Static', NULL, 'SystemLogList', 0, 0, 1, NULL, NULL, '76d52c6373fe7da00535ed4f0ea125bd', NULL, '0867a2f15dca83f635b63da83df334da', NULL);
UPDATE `meta_store_management__versioned_view` SET DependencyProductVersion=(SELECT DependencyProductVersion FROM `meta_store_management__module_specific_version` WHERE `key`='base_1.0.0' AND tenantId=1) WHERE `key` in ('base_event_ModelChangeEvent_BusinessLogList', 'base_event_ModelChangeEvent_SystemLogList', 'base_event_ArchiveLog_ArchiveLogList');
UPDATE `meta_store_management__versioned_view` SET FromModule=(SELECT `id` FROM `meta_store_management__module_specific_version` WHERE `key`='base_1.0.0' AND tenantId=1) WHERE `key` in ('base_event_ModelChangeEvent_BusinessLogList', 'base_event_ModelChangeEvent_SystemLogList', 'base_event_ArchiveLog_ArchiveLogList');

三方日志

若系统内置的操作日志无法满足用户需求,也可显式调用 Trantor SDK 写三方日志,SDK 只负责日志持久化,日志展示与内置操作日志共用。日志展示入口有两个地方:

  1. 日志关联的模型编辑/详情页。
  2. 企业日志列表。

模型编辑/详情页日志展示与模型强关联,在利用 SDK 写入三方日志时需要关联模型信息,必填字段有:

  • 模型名称(展示时使用)
  • 模型 key(日志过滤时使用)
  • 模型数据记录 id
  • 模型变更事件类型(仅限: Create/Update/Delete)
  • 操作动作

具体使用方式请参考以下示例,下面针对创建/更新/删除流程分别举例。

1. 自定义创建日志

@FunctionImpl(name = "创建商品默认实现")
public class CreateItemFuncImpl implements CreateItemFunc {
@Override
public ItemBO execute(ItemBO item) {
ThirdPartyEvent thirdPartyEvent = new ThirdPartyEvent();
thirdPartyEvent.setActionKey("新增");
// 第三方日志挂载模型上展示时配置
thirdPartyEvent.setEventType(ModelEventType.Create);
thirdPartyEvent.setModelName("商品");
thirdPartyEvent.setRecordId("1");
thirdPartyEvent.setModelKey("item_ItemBO");
// 自定义日志数据
Map<String, Object> data = new HashMap<>();
data.put("name", "手机");
data.put("price", 2000);
thirdPartyEvent.setData(data);
LogEventPublisher.publish(thirdPartyEvent);
return null;
}
}

2. 自定义更新日志

@FunctionImpl(name = "更新商品默认实现")
public class UpdateItemFuncImpl implements UpdateItemFunc {
@Override
public ItemBO execute(ItemBO item) {
ThirdPartyEvent thirdPartyEvent = new ThirdPartyEvent();
thirdPartyEvent.setActionKey("调价");
// 第三方日志挂载模型上展示时配置
thirdPartyEvent.setEventType(ModelEventType.Update);
thirdPartyEvent.setModelName("商品");
thirdPartyEvent.setRecordId("1");
thirdPartyEvent.setModelKey("item_ItemBO");
// 更新商品价格
Map<String, Object> before = new HashMap<>();
before.put("name", "手机");
before.put("price", 2000);
thirdPartyEvent.setBefore(data);
Map<String, Object> after = new HashMap<>();
after.put("name", "手机");
after.put("price", 3000);
thirdPartyEvent.setData(data);
LogEventPublisher.publish(thirdPartyEvent);
return null;
}
}

3. 自定义删除日志

@FunctionImpl(name = "删除商品默认实现")
public class DeleteItemFuncImpl implements DeleteItemFunc {
@Override
public ItemBO execute(ItemBO item) {
ThirdPartyEvent thirdPartyEvent = new ThirdPartyEvent();
thirdPartyEvent.setActionKey("删除");
// 第三方日志挂载模型上展示时配置
thirdPartyEvent.setEventType(ModelEventType.Delete);
thirdPartyEvent.setModelName("商品");
thirdPartyEvent.setRecordId("1");
thirdPartyEvent.setModelKey("item_ItemBO");
LogEventPublisher.publish(thirdPartyEvent);
return null;
}
}

定时清理

为了防止日志无限增长,撑爆数据库,Trantor 提供了日志定时清理功能,每天凌晨 03:30:00 开始清理,默认最大保留时间为 15 天,可通过环境变量: TRANTOR_EVENT_MAX_RESERVED 指定(单位为天),eg:

TRANTOR_EVENT_MAX_RESERVED=30