Activepieces 审计日志(Audit Logs)全解:事件模型、捕获架构与生产级查询索引实践
2026/9/12 16:34:40 网站建设 项目流程

Activepieces 审计日志(Audit Logs)全解:事件模型、捕获架构与生产级查询索引实践

【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces

本文以 Activepieces 开源仓库中的审计日志模块为核心,完整解析其事件模型、基于事件总线的捕获架构、平台管理员查询 API,以及从生产事故中沉淀出的索引设计与事件发射最佳实践。读完本文,你将掌握audit_event表的字段与索引设计、ApplicationEvent判别联合类型、GET /v1/audit-events的过滤与游标分页用法,以及如何避免"事件漏记"与"大表查询超时"这两类高发问题。

一、审计日志的定位与启用条件

Activepieces 的审计日志(Audit Logs)用于记录安全相关的用户与系统操作,为合规审计(compliance)与取证(forensics)提供依据。所有事件被持久化到audit_event表,并可由平台管理员(platform admin)查询。该功能仅对Enterprise / Cloud版本开放,由平台套餐字段platform.plan.auditLogEnabled控制。

在服务端,这一门控是在路由注册时通过 Fastify 的preHandler钩子强制执行的。参见 audit-event-module.ts:

export const auditEventModule: FastifyPluginAsyncZod = async (app) => { auditLogService(app.log).setup() app.addHook('preHandler', platformMustHaveFeatureEnabled((platform) => platform.plan.auditLogEnabled)) await app.register(auditEventController, { prefix: '/v1/audit-events' }) }

这意味着:当套餐未开启auditLogEnabled时,/v1/audit-events相关请求会被直接拦截;同时前端平台管理页面的审计日志入口也会基于同一字段禁用(见下文 React Query hooks 中的enabled: platform.plan.auditLogEnabled)。

二、数据模型:ApplicationEvent 联合类型与 audit_event 表

2.1 ApplicationEvent:可审计事件的判别联合

所有可审计的事件类型被建模为一个discriminated union(判别联合),定义在 packages/core/shared/src/lib/ee/audit-events/index.ts。该联合由 20 个 Zod schema 组合而成(AgentAuditEventConnectionEventVariableEventFlowCreatedEventFlowDeletedEventFlowUpdatedEventFlowPiecesUpgradedEventFlowPiecesRevertedEventFlowPublishedEventFlowActivatedEventFlowDeactivatedEventFlowRunEventAuthenticationEventFolderEventSignUpEventSigningKeyEventProjectRoleEventProjectReleaseEventProjectReplacedEventFlowApprovalEvent),以action字段作为判别键,每个事件类型都复用一组公共信封字段:

const BaseAuditEventProps = { ...BaseModelSchema, // id / created / updated platformId: z.string(), projectId: z.string().optional(), projectDisplayName: z.string().optional(), userId: z.string().optional(), userEmail: z.string().optional(), ip: z.string().optional(), }

公共信封 + 类型化的data载荷,既保证了入库数据的结构一致,也让消费端(摘要生成、前端渲染、测试)可以按事件类型安全地访问载荷。

2.2 ApplicationEventName:39 个事件名枚举

ApplicationEventName是事件的字符串枚举。当前源码中共有39 个值(早期内部笔记记录为 27 个,仓库已持续扩充),按领域可归纳为下表:

领域事件名
流程生命周期flow.createdflow.deletedflow.updatedflow.publishedflow.activatedflow.deactivated
流程运行flow.run.startedflow.run.finishedflow.run.resumedflow.run.retried
流程维护flow.pieces.upgradedflow.pieces.revertedflow.approval.requestedflow.approval.grantedflow.approval.rejectedflow.approval.withdrawn
文件夹folder.createdfolder.updatedfolder.deleted
连接connection.upsertedconnection.deleted
变量variable.upsertedvariable.deletedvariable.value.revealed
Agentagent.createdagent.updatedagent.deletedagent.publishedagent.unpublished
认证与用户user.signed.upuser.signed.inuser.password.resetuser.email.verified
平台治理signing.key.createdproject.role.createdproject.role.updatedproject.role.deletedproject.release.createdproject.replaced

值得注意的细节:variable.value.revealed(变量值被查看)与signing.key.created(签名密钥创建)属于安全敏感事件,说明审计范围不止覆盖业务流程,还覆盖凭证类操作。而project.replaced事件的数据载荷中包含了 flows / tables / folders / connections 各自的 created / updated / deleted / unchanged 计数与outcomedurationMs等字段,为项目替换类批量操作提供了完整的事后统计(见 index.ts)。

2.3 audit_event 表结构与索引

audit_event表以 TypeORMEntitySchema定义,见 audit-event-entity.ts。字段如下:

字段类型可空说明
id/created/updatedBaseColumnSchemaPart提供的公共列
platformIdString所属平台,外键关联platform,级联删除
projectIdString所属项目(后台/系统级事件可无项目)
actionString事件名,对应ApplicationEventName
userEmailString操作者邮箱(系统事件可空)
projectDisplayNameString项目显示名快照
datajsonb类型化事件载荷,JSONB 存储
ipString客户端真实 IP(请求来源时填充)
userIdString操作者用户 ID

表上定义了 4 个复合索引(audit-event-entity.ts):

  • audit_event_platform_id_project_id_user_id_action_idx(platformId, projectId, userId, action),最窄组合,覆盖最常见的过滤场景;
  • audit_event_platform_id_user_id_action_idx(platformId, userId, action)
  • audit_event_platform_id_action_idx(platformId, action)
  • audit_event_platform_id_created_id_desc_idx(platformId, created, id)——这个索引与分页排序强相关,详见第五节。

data采用 jsonb 而非拆列,使事件载荷可以随业务演进自由扩展,无需为每个新事件类型做 DDL 迁移。

三、事件捕获架构:基于事件总线的解耦监听

审计日志的核心设计思想是通过事件总线(applicationEvents)解耦捕获:业务代码只负责"发事件",审计模块只负责"听事件",二者互不感知。

auditLogService.setup()在模块初始化时调用(audit-event-service.ts):

export const auditLogService = (log: FastifyBaseLogger) => ({ setup(): void { applicationEvents(log).registerListeners(log, { userEvent: (log) => async (params) => { rejectedPromiseHandler(auditLogRepo().save(params), log) }, workerEvent: (log) => async (_projectId, params) => { rejectedPromiseHandler(auditLogRepo().save(params), log) }, }) }, // ... })

两个监听器分别对应两类事件来源,均为 fire-and-forget(异步落库、异常不抛出):

  • userEvent:由 HTTP 请求触发的用户操作(如登录、编辑流程),事件参数中已携带完整的userEmailprojectDisplayNameip等上下文;
  • workerEvent:由后台 Worker 触发的操作(如流程运行、项目替换),以(projectId, params)形式传入,事件总线会补上idcreatedupdated等信封字段。

总线实现在 application-events.ts:sendUserEvent()会对请求来源做上下文丰富(enrichAuditEventParam),包括解析出真实用户 ID、项目、项目显示名、用户邮箱与真实客户端 IP,再把事件分发给所有userEventListenerssendWorkerEvent()则直接构造事件并广播给workerEventListenersrejectedPromiseHandler保证落库失败不会反噬主业务流程。

3.1 上下文如何被丰富

对于请求来源的事件,extractMetaInformation()会从 Fastify 请求中提取:

  • platformId:取principal.platform.id
  • projectId:取request.projectId ?? principal.projectId
  • userId:经authenticationUtils.extractUserIdFromRequest()解析;
  • ip:经networkUtils.extractClientRealIp()CLIENT_REAL_IP_HEADER指定的请求头提取。

随后事件总线再依据userId反查用户邮箱、依据projectId反查项目显示名,把"裸事件"丰富成可读、可审计的完整记录(application-events.ts)。这也是为什么审计日志能直接展示"谁、在哪个项目、什么 IP、做了什么"——这些信息在事件源头发送时可能并不完整。

四、查询 API 与前端集成

4.1 GET /v1/audit-events

路由挂载于packages/server/api/src/app/ee/audit-logs/audit-event-module.ts,前缀/v1/audit-events仅平台管理员可访问securityAccess.platformAdminOnly([PrincipalType.SERVICE, PrincipalType.USER]))。控制器将查询参数透传给auditLogService.list(),返回SeekPage<ApplicationEvent>,按created倒序排列。

请求参数由 Zod schemaListAuditEventsRequest定义(index.ts):

参数类型说明
limitnumber(可选)每页条数,未传时服务端默认 20
cursorstring(可选)游标分页令牌
actionstring[](可选,数组)按事件名过滤,支持多个
projectIdstring[](可选,数组)按项目过滤,支持多个
userIdstring(可选)按用户 ID 过滤
createdBeforestring(可选)只返回该时间点之前的事件
createdAfterstring(可选)只返回该时间点之后的事件

服务端的过滤实现(audit-event-service.ts)以platformId为强制过滤条件(平台数据隔离),再按userId等值、action/projectIdIN列表、created的区间逐层叠加andWhere,最后交给buildPaginator完成游标分页。集成测试 audit-event.test.ts 验证了两点:管理员能列出本平台全部事件;非 owner(PlatformRole.MEMBER)请求返回403 FORBIDDEN

4.2 前端:API client、React Query hooks 与管理页面

前端数据链路清晰分层:

  • audit-events-api.ts:封装GET /v1/audit-events请求;
  • audit-log-hooks.ts:React Query 的useAuditLogs()hook,把 URL 查询参数(cursor、limit、action[]、projectId[]、userId、createdBefore/After)映射为 API 参数,并设置enabled: platform.plan.auditLogEnabled—— 套餐未开启时直接不发起请求;
  • UI 页面位于 packages/web/src/app/routes/platform/security/audit-logs/,供平台管理员在"安全"菜单下按事件类型筛选、按时间区间查询。

五、从生产事故中沉淀的实战陷阱(Gotchas)

该模块的知识库笔记记录了几条极具工程价值的生产教训,以下逐条展开。

5.1 事件必须由"执行操作的服务"发出,而非调用方

事故 #14591 的根因:flow 类事件当初在flow.controller.ts中发射,导致所有绕过 controller 直接调用 flow 服务代码的路径——包括 15 个 MCP flow 工具、app-connection.handler.tsworker-rpc-service.tsproject-state-helper.tsplatform-teardown-jobs.ts——在修改流程时完全没有留下审计记录。而 flowruns从未出现该问题,因为运行事件经由flow-run-service.ts内的flowRunSideEffects发出。

由此形成两条硬性规范:

  1. *-side-effects.ts钩子放进执行操作的服务内部,默认开启事件发射,而不是放在 controller 层或让每个调用方各自发射;
  2. 批量/系统路径若确实需要静默(如 project release apply、platform teardown),必须显式传emitEvents: false退出,让"这次不记审计"成为一个可审查的显式决策,而非意外遗漏。

此外,schema 中ip是可选的——正确做法是把它作为 controller 传入的一个可选参数向下传递,而不是为了让事件拿到 IP 就把发射逻辑留在 controller 层。仓库中新增的 mcp-flow-audit-trail.test.ts 正是针对 MCP 工具链审计追踪的回归测试。

5.2 排序与索引:分页必须覆盖排序列

列表接口的排序规则是created DESC, id DESC——Paginator会自动追加id作为平局决胜键(withIdTiebreaker),因此索引必须同时覆盖这两列

  • 只有(platformId, created DESC):查询计划会在索引之上叠加 Incremental Sort 节点;
  • 只有(platformId, created DESC, id DESC):是纯索引扫描(plain index scan),即当前实体中定义的audit_event_platform_id_created_id_desc_idx
  • 若两者都没有:Postgres 只能借助platformId开头的action索引读完整平台的所有行,再全量排序后只返回一页(如 11 条),导致语句超时、页面 500(对应事故 GIT-1705)。

文档记录的生产规模数据可佐证问题的严重性:Cloud 生产环境(2026 年 8 月)audit_event已达约 3.62 亿行 / 475 GB,单个平台约 650 万行,错误查询计划代价高达 740 万且永不结束。同时该表从不清理(GIT-1574),因此任何新增查询形态都需要设计覆盖排序的索引,而不只是覆盖过滤条件

5.3 在生产大表上建索引是"运维操作",不是"迁移步骤"

在 475 GB 的表上,CREATE INDEX CONCURRENTLY需要运行数小时。而项目迁移在main.ts中、服务开始监听之前执行——启动期构建索引永远无法及时通过健康检查,部署会被回滚到一个只建了一半的索引上。因此规范做法是:

  1. 在部署前手工构建索引,让迁移里的IF NOT EXISTS直接跳过(no-op);
  2. CREATE INDEX CONCURRENTLY同样受statement_timeout约束,构建会话中需先SET statement_timeout = 0;若角色级超时已设置,启动期路径会直接失败;
  3. 被中断的 CONCURRENTLY 构建会留下indisvalid = false的索引,普通IF NOT EXISTS重试会跳过它并"报告成功",但查询规划器永远不会使用这个索引——这也是 1820 号迁移要检查pg_index.indisvalid并在重建前删除无效残留的原因。

5.4 游标分页的演进:同一秒内的事件不再被跳过

如果读到过"该分页器用DATE_TRUNC('second', created)生成游标"的旧资料,那是过时信息。当前实现改为选择created::text,并生成复合游标(created < c) OR (created = c AND id < i),彻底修复了"同一秒内的事件跨页被跳过"的旧缺陷。也就是说,分页在createdid两层上都是稳定、唯一的。

六、摘要生成与测试基建

summarizeApplicationEvent()(index.ts)将每个事件渲染成一句话的人类可读摘要。其中flow.updated最复杂:它依据FlowOperationType(ADD_ACTION、UPDATE_ACTION、DELETE_ACTION、CHANGE_NAME、LOCK_AND_PUBLISH、MOVE_ACTION、ADD/DUPLICATE/DELETE/MOVE_BRANCH、ADD/UPDATE/DELETE_NOTE 等 20 余种操作)生成精确的细节描述,例如Added action "Send Email" to "Order Flow" Flow.Deleted actions "step_1, step_2" from "Order Flow" Flow.。运行类事件则输出Flow run <id> is started / finished / retried from a failed step,审批类事件附带rejectionReason

配套的buildMockEvent()(mock-event-builder.ts)为每个事件名生成一个带完整合法载荷的 typed mock(统一使用ip: '127.0.0.1'等固定测试数据),既用于事件目的地(event-destination)测试投递,也是后续新增事件类型时"一键产出样例数据"的基座。

七、关键文件索引

  • 服务端模块:packages/server/api/src/app/ee/audit-logs/(含 audit-event-module.ts、audit-event-service.ts、audit-event-entity.ts)
  • 事件总线:packages/server/api/src/app/helper/application-events.ts
  • 事件类型与工具:packages/core/shared/src/lib/ee/audit-events/(ApplicationEvent联合、ApplicationEventName枚举、summarizeApplicationEvent()buildMockEvent()
  • 前端:audit-events-api.ts、audit-log-hooks.ts、审计日志管理页面
  • 集成测试:packages/server/api/test/integration/cloud/audit-event/(含 audit-event.test.ts 与 mcp-flow-audit-trail.test.ts)
  • 面向用户的文档:docs/admin-guide/security/audit-logs/(每种事件类型一个文档页)

结语

Activepieces 的审计日志模块是一套"事件模型 + 事件总线 + 门控查询"三位一体的实现:判别联合的事件类型保证了数据结构的可扩展性,applicationEvents总线实现了业务与审计的解耦,而auditLogEnabled门控与platformAdminOnly权限控制保证了只有合规范围内的人员能访问。真正值得借鉴的是那些踩坑记录——把事件发射放在服务内部、为created + id排序设计复合索引、在超大规模表上以运维流程而非启动迁移的方式建索引——这些实践同样适用于任何需要长期累积、按时间倒序查询的海量审计表。

【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询