Backstage Auditor 核心服务解析:@backstage/backend-defaults 的审计 API 与实现机制
2026/9/13 18:56:02 网站建设 项目流程

Backstage Auditor 核心服务解析:@backstage/backend-defaults 的审计 API 与实现机制

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

report-auditor.api.md是 Backstage 仓库中@backstage/backend-defaults包 auditor 入口的 API Extractor 报告文件,它冻结并声明了 Auditor 核心服务的全部公开 API:事件类型、默认实现DefaultAuditorService、服务工厂auditorServiceFactory以及基于独立 Winston 日志器的WinstonRootAuditorService。本篇以该 API 报告为主体骨架,结合 auditor 入口源码、单元测试与 核心服务文档 中的配置规范,讲解每个导出成员的语义、底层实现、backend.auditor配置项的解析逻辑,以及插件侧使用 AuditorService 的完整姿势,帮助你在自研后端插件中落地可检索、可分级、可合规的安全审计日志。

API 报告中的公开导出总览

API 报告 由 API Extractor 自动生成,标注"Do not edit",其作用是在接口发生不兼容变更时提供可 diff 的契约基线。报告中声明的@public导出可归为四类:

类别导出成员作用
事件类型AuditorEventAuditorEventOptions<TMeta>AuditorEventStatusAuditorEventActorDetailsAuditorEventRequest描述一条审计事件的结构、创建入参与状态机
日志函数AuditorLogFunction审计事件落地到日志系统的回调签名
默认实现DefaultAuditorService实现AuditorService接口的核心类
服务装配auditorServiceFactoryWinstonRootAuditorServiceWinstonRootAuditorServiceOptions分别对应"复用现有 logger"与"独立 Winston logger"两种接入方式

类型导入关系值得注意:AuditorServiceAuditorServiceEventAuditorServiceEventSeverityLevelAuthServiceHttpAuthServicePluginMetadataService均定义在@backstage/backend-plugin-api中,@backstage/backend-defaults只负责提供实现。也就是说,插件依赖注入面向的是接口,实现细节由 backend-defaults 提供。

事件类型系统:AuditorEvent 与其子类型

报告中最核心的类型是事件本体与它的状态联合:

export type AuditorEvent = { plugin: string; eventId: string; severityLevel: AuditorServiceEventSeverityLevel; actor: AuditorEventActorDetails; meta?: JsonObject; request?: AuditorEventRequest; } & AuditorEventStatus; export type AuditorEventStatus = | { status: 'initiated' } | { status: 'succeeded' } | { status: 'failed'; error: Error };
  • AuditorEventStatus是一个可辨识联合(discriminated union):事件生命周期只有三种状态——initiated(操作开始)、succeeded(成功完成)、failed(失败并强制携带error: Error)。这种结构保证了失败日志一定包含错误对象,配合 winston 的errors({ stack: true })格式器可直接输出堆栈。
  • AuditorEventActorDetails记录"谁在做":actorId(用户 entityRef 或服务主体)、iphostnameuserAgent,全部为可选字段。
  • AuditorEventRequest只保留urlmethod两个字段——即原始 Express 请求不会整体入日志,只留痕定位信息。
  • AuditorEventOptions<TMeta extends JsonObject>是插件侧createEvent的入参形态:必填eventId,可选severityLevelrequestmeta,同样与状态联合交叉。

在 DefaultAuditorService 的log方法 中可以看到这些字段如何被组装成最终的AuditorEventplugin取自PluginMetadataService.getId()severityLevel缺省为'low'actor从请求与凭据中解析;meta为空对象时会被规范化为undefined,避免在日志里留下无意义的空对象。

DefaultAuditorService:三段式审计事件与执行者解析

DefaultAuditorService实现了AuditorService接口,构造方式为静态工厂:

static create( logFn: AuditorLogFunction, deps: { auth: AuthService; httpAuth: HttpAuthService; plugin: PluginMetadataService; }, ): DefaultAuditorService;

它的唯一依赖是一组AuditorLogFunction(事件落日志的回调)和三个核心服务。核心入口是createEvent

async createEvent(options) { await this.log({ ...options, status: 'initiated' }); return { success: async params => { /* 合并 meta 后记录 status: 'succeeded' */ }, fail: async params => { /* 记录 error 与 status: 'failed' */ }, }; }

见 DefaultAuditorService.ts 的 createEvent 实现。其行为有三点关键细节:

  1. 调用即记录createEvent被调用时立即输出第一条initiated日志,随后通过返回的success/fail闭包补记终态。一次完整的操作最多产生两条审计日志。
  2. meta 渐进合并success/fail的入参可以携带各自的meta,会与createEvent时的meta浅合并(后者覆盖前者)。单元测试 用 "use root meta" 用例验证了这一点:initiated 阶段记录{ initiated: 'test' },succeeded 阶段记录{ initiated, succeeded },failed 阶段记录{ initiated, failed }
  3. 失败必含 Errorfail({ error })会把error提升到事件顶层字段,与AuditorEventStatusfailed分支的必填约束一致。

执行者(actor)解析逻辑在私有方法getActorId中(源码):

  • 无 HTTP 请求的后台任务:使用auth.getOwnServiceCredentials(),执行者就是当前服务本身;
  • 有请求时:通过httpAuth.credentials(request)从请求中解析凭据,解析失败会抛出ForwardedError('Could not resolve credentials')——即审计不降级,身份不明直接让操作失败;
  • 凭据是用户主体时取principal.userEntityRef,是服务主体时取principal.subject,否则返回undefined

DefaultAuditorService 测试 用mockServices验证了三种状态落日志的完整字段:{ eventId, status, plugin, severityLevel: 'low', actor: {} },其中actor为空对象正是因为 mock 凭据既非用户也非服务主体时各字段均为undefined

auditorServiceFactory:复用现有 logger 的默认装配方式

auditorServiceFactory 是 backend-defaults 提供给插件体系的标准服务工厂,注册在coreServices.auditor上,依赖五个核心服务:

export const auditorServiceFactory = createServiceFactory({ service: coreServices.auditor, deps: { config: coreServices.rootConfig, logger: coreServices.logger, auth: coreServices.auth, httpAuth: coreServices.httpAuth, plugin: coreServices.pluginMetadata, }, factory({ config, logger, plugin, auth, httpAuth }) { const auditLogger = logger.child({ isAuditEvent: true }); const severityLogLevelMappings = getSeverityLogLevelMappings(config); return DefaultAuditorService.create(event => { /* ... */ }, { plugin, auth, httpAuth }); }, });

工厂内部有两处设计要点:

  • 审计日志与业务日志隔离标记:通过logger.child({ isAuditEvent: true })派生子日志器,所有审计行都会带上isAuditEvent: true字段,便于在日志管道中精确过滤审计数据流;
  • severity → log level 动态路由:根据配置解析出的severityLogLevelMappings,用auditLogger[mappings[event.severityLevel]](...)动态调用对应的 winston 级别方法(debug/info/warn/error)。当事件包含error时(即 failed 状态),会用除 error 外的其余事件字段创建一个 child logger 再记录,避免 Error 对象被当作普通 meta 序列化。日志的 message 固定为`${event.plugin}.${event.eventId}`,形成插件.事件的点号命名空间。

WinstonRootAuditorService:为审计建立独立日志通道

WinstonRootAuditorService提供另一种接入路径:不依赖插件体系已有的 logger,而是自建一个独立的 winston 日志器,适合需要将审计日志写入独立文件、独立传输通道(如专用 SIEM 通道)的场景。

报告与 WinstonRootAuditorService.ts 中定义的选项类型:

export type WinstonRootAuditorServiceOptions = { meta?: JsonObject; format?: Format; transports?: winston.transport[]; };

static create(options?)内部通过WinstonLogger.create构建日志器,固定注入meta: { service: 'backstage' }level: 'info',格式器为:

export const defaultFormatter = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json(), );

即输出带时间戳、展开错误堆栈的 JSON 行。另有一个公开的auditorFieldFormat,作用是在每条日志上追加isAuditEvent: true标记,并在create时无条件合入最终 format 链(winston.format.combine(auditorFieldFormat, options?.format ?? defaultFormatter))。若传入options.meta,会再派生一层 child logger 注入。

forPlugin(deps)方法与工厂方式不同,其 deps 中多一个config: Config(而非logger):

forPlugin(deps: { auth: AuthService; config: Config; httpAuth: HttpAuthService; plugin: PluginMetadataService; }): AuditorService { const severityLogLevelMappings = getSeverityLogLevelMappings(deps.config); return DefaultAuditorService.create(event => { /* 同工厂:按 severity 路由级别 */ }, deps); }

从源码结构看,它本质上是一个"根上下文":文档示例 展示的标准用法是通过createRootContext()创建一次,然后在每个插件的factory里调root.forPlugin(...)得到该插件专属的AuditorService实例,既共享底层传输通道,又保持插件维度隔离。WinstonRootAuditorService 测试 验证了forPlugin返回的确实是DefaultAuditorService实例,且 initiated/succeeded 事件按预期流经内部log方法。

backend.auditor.severityLogLevelMappings 配置解析

两个实现共用 utils.ts 中的getSeverityLogLevelMappings(config)来解析配置:

  • 配置根键为backend.auditor,读取severityLogLevelMappings下的low/medium/high/critical四个键;
  • 使用 zod 枚举['debug', 'info', 'warn', 'error']校验每个值,缺省默认映射为low: 'debug'medium/high/critical均为'info'
  • 任一值非法时抛出InputError,错误信息会明确指出具体键名、收到的非法值和全部合法取值。

对应的app-config.yaml写法(可只覆盖单个级别):

backend: auditor: severityLogLevelMappings: low: debug medium: info high: warn critical: error

默认映射的含义是:low事件默认落在 debug 级别(生产环境通常被日志级别过滤掉),而 medium/high/critical 默认都记为 info——如需让高危事件更醒目,可将high/critical调至warn/error,这正是 核心服务文档 中"Severity Log Level Mappings"章节给出的用法。

插件中使用 AuditorService:接口、命名规范与 meta 约定

插件侧只依赖@backstage/backend-plugin-apiAuditorService接口,通过路由选项注入。官方文档 给出的 Express 路由集成示例:

export async function createRouter(options: RouterOptions): Promise<express.Router> { const { auditor } = options; const router = Router(); router.use(express.json()); router.post('/my-endpoint', async (req, res) => { const auditorEvent = await auditor.createEvent({ eventId: 'my-endpoint-call', request: req, meta: { // ... metadata about the request }, }); try { // ... process the request await auditorEvent.success(); res.status(200).json({ message: 'Succeeded!' }); } catch (error) { await auditorEvent.fail({ error }); res.status(500).json({ message: 'Failed!' }); throw error; } }); return router; }

传入request: req后,实现层会自动提取iphostnameuser-agent进入actororiginalUrlmethod进入request字段,并从请求凭据解析actorId

命名规范(源自核心服务文档,并在AuditorEventOptionseventId文档注释中重申):

  • kebab-caseeventId使用如user-loginfile-download的形式;
  • eventId表逻辑分组:如entity-fetch归组所有实体读取操作,location-mutate归组所有 location 变更;
  • 具体动作放meta:用meta.queryTypemeta.actionType等字段表达组内细分动作,例如eventId: 'entity-fetch'+meta: { queryType: 'by-id' }
  • 避免与 pluginId 冗余的前缀:插件上下文已经由plugin字段单独承载。

常用 meta 键约定

Key说明格式示例
queryType查询类型kebab-case 字符串allby-idby-nameby-queryby-refsancestryby-entity
actionType变更动作类型kebab-case 字符串createdeleterefresh
entityRef实体全引用[kind]:[namespace]/[name]component:default/my-component
locationRef被操作的 location任意位置引用字符串url:https://example.com/catalog-info.yaml
uid对象唯一标识任意有效唯一 ID 字符串9a4e740b-e557-427f-b9f2-0d4f092b1c1e

典型的写操作审计事件形如:{ "eventId": "entity-mutate", "meta": { "actionType": "delete", "uid": "some-entity-uid", "entityRef": "component:default/petstore" }, "severityLevel": "medium" }

按 核心服务文档 的指引,plugins/catalog-backendsrc/service/createRouter.ts是该服务在真实插件中的完整参考实现,Catalog 的 Audit Events 文档则示范了如何为一个插件的eventId清单撰写配套文档。选型上应记住:审计服务面向安全相关与合规类事件(会话管理、数据访问变更、配置变更等),一般业务日志仍应走标准LoggerService

小结:从 API 契约到可落地的审计能力

report-auditor.api.md声明的这组导出,构成了 Backstage 后端审计能力的完整闭环:AuditorEvent*系列类型约束事件形状与三态状态机;DefaultAuditorService负责执行者解析、meta 合并与日志回调;auditorServiceFactoryWinstonRootAuditorService分别给出"复用现有 logger 并打标isAuditEvent"和"独立 Winston 通道(默认 JSON 格式 + 时间戳 + 堆栈展开)"两种装配路径;backend.auditor.severityLogLevelMappings配置则以 zod 校验支撑 severity 到日志级别的灵活路由。理解这层 API 与实现后,你可以在任何 Backstage 后端插件中以统一、可过滤、带执行者上下文的格式输出审计事件。

【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage

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

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

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

立即咨询