1. 页面概览

1.1 是什么

「审计日志」页(对应前端源码 action/web/src/views/apollo/AuditLogsPage.vue,页面标题「Apollo 审计日志」,TAD-11 批次 2 平台管理组)是 LightApollo 的 HTTP 控制面操作审计查询页:以只读方式分页检索后端记录的每一次 API 调用,并用「汇总卡片 + 多维筛选 + 行点击展开详情」把「谁、在什么时候、调了哪个接口、带了什么参数、成功还是被拒、耗时多少」完整还原出来。数据源是后端每收到一次请求就异步落库的 apollo_audit_logs 表(HTTP 审计),写入者是注册在根路由组、位于 requestIDMiddleware 之后的 httpAuditMiddleware——它覆盖登录/注册/Agent 拉取等公开端点与全部受保护端点,只有探针(/health/api/health)与 API 文档(/api/v1/docs)属噪声被跳过。所以本页的定位是「Apollo 控制面访问的留痕台账」:从账号登录、创建项目、增删成员,到某次部署发起、渠道改指向、签名者启停,凡经过 HTTP 网关的操作都能在这里查到对应行。

在实际运维中,本页最常用的两个场景分别是「行为留痕取证」与「慢接口/失败归因」:前者在发生越权尝试、误操作删数据、凭据盗用等事件后,用 denied 结果筛选或按时间窗拉出对应记录,还原「发生了什么、是谁干的、带了什么参数」;后者在系统异常时按时间窗把某段时间的全部调用拉出来,用耗时(ms)列定位慢请求、用状态码列粗筛失败端点。需要注意,审计行里**没有保存响应头里的 X-Request-ID(即 envelope 的 request_id)**——该 ID 只在当次请求的响应里返回,用于排障对照,并不随记录落库;跨请求关联只能靠 timestamp + username + method + path 近似匹配(见 8 章)。此外日志表目前没有内置保留期与自动清理任务,演示环境会持续累积(量级通常不大;如需删除需由数据库侧手工处理)。

先分清本页与相邻数据的关系:HTTP 审计(本页数据,表 apollo_audit_logs)记录的是「一次 HTTP 请求」的进出信息——方法、路径、状态码、耗时、客户端 IP、请求体与查询串(脱敏后)、结果归类;而业务生命周期审计是另一套东西——部署推进、漂移检出这类业务事件由各 handler 调 recordAudit → 平台审计服务 LogEventRefproduct="apollo")写入按产品名命名的哈希链审计表 apollo_audit_log(单数,见 5.4)。二者互不相见:本页永远查不到 DEPLOYMENT_START 这类业务事件码,页面上 action 输入框的占位示例「如 DEPLOYMENT_START」其实是一处与真实取值口径不一致的提示(存储的 action 恒为「HTTP 方法 + 空格 + 路径」,如 POST /api/v1/deployments/start),详见 7 章与 8 章。

典型使用链路(从操作到留痕的完整闭环):① 平台管理员(super)登录 Apollo——这一次 POST /api/v1/auth/login 本身就会被记一条审计(此时请求尚未经过 access.Authn,行内用户栏为空);② 到「项目管理」页新建项目 core-payPOST /api/v1/projects 成功返回 201,中间件把它归为 success 落库;③ 切回本页点「查询」——注意查看审计页这个动作本身也会被记录GET /api/v1/audit-logs),列表里最新一条往往就是你自己;④ 在顶部汇总卡片看日志总数、近 24 小时量与按 action 分布(此时会看到 POST /api/v1/auth/loginPOST /api/v1/projectsGET /api/v1/audit-logs 等计数);⑤ 在 action 输入框填入 POST /api/v1/projects 精确过滤,行展开后核对请求体——若创建项目时带了敏感字段会被脱敏成 ***;⑥ 再用结果下拉选 denied,过滤出 401/403 的被拒尝试,判断是否有越权扫描;⑦ 翻页、重置、改 from/to 时间窗,反复核对。整页是纯只读的查询界面,没有任何写按钮,也不触发任何写请求。

从「看得见什么」的角度再强调两条边界,方便日常使用不困惑:其一,审计是异步落库(channel + worker,见 5.4),刚完成的操作要等 worker 消费才可查,存在秒级延迟窗口;其二,匿名流量(未登录访问公开端点、Agent pull/report)也会进表但用户栏为空,denied 大类里既有「登录失败/凭据错」也有「Agent 上报鉴权失败」等非人类操作,统计时需结合 path 列甄别。换句话说:本页证明的是「HTTP 层发生过什么」,而不是「业务层为什么失败」——后者的错误详情要么在展开行的 error_message(当前恒空,见 8 章),要么在各业务页的失败提示里。

1.2 核心价值/能力表

能力说明对应页面操作
全量留痕后端每次 API 调用(含公开端点)脱敏后落库,按 id 倒序查询页面首次加载列表
汇总卡片日志总数 / 近 24 小时 / 按 action 计数一目了然,卡片随 summary 数据渲染顶部汇总区
多维筛选action 精确匹配 + result 三态下拉(success/failure/denied)+ from/to 时间范围筛选栏 + 「查询」
一键重置清空四个筛选条件并回到第 1 页重新拉取「重置」按钮
行点击展开展开行展示 action/resource/客户端 IP/user_agent 与三段脱敏详情点击任意日志行
状态着色状态码 2xx 绿 / 4xx 橙 / 5xx 红;result 徽标三态配色表格状态码与结果列
结果三态归类2xx → success、401/403 → denied、其余 4xx/5xx → failure,语义在服务端定结果徽标与 result 筛选
分页浏览每页 20 条、id 倒序,上一页/下一页切换并保留筛选表格下方分页区
只读零副作用页面无写操作,打开本页只会产生自身列表/汇总的 GET 记录全部操作

1.3 一句话总结

本页是 Apollo 控制面访问的「黑匣子查询台」——HTTP 层谁在何时调了哪个接口、成败几何、耗时多少、带了什么脱敏后的参数,全部在此留痕可按动作/结果/时间检索,业务生命周期事件另存别处、不在本页出现。

2. 访问入口

2.1 路由与菜单

路由 path/apollo/admin/audit-logs
路由 nameApolloAdminAuditLogs
meta.titleApollo 审计日志
父路由/apollo(组件 ApolloLayoutmeta.title: 'Apollo'
侧边栏入口ApolloLayout 侧边栏「平台管理」分组下的「审计日志」,位于「项目管理」之后、「签名者白名单」之前
前端源码action/web/src/views/apollo/AuditLogsPage.vue
API 客户端action/web/src/api/apolloClient.js
路由注册action/web/src/router/index.js/apollo 子路由 children 下(507-511 行),component: ApolloAuditLogsPage,挂 ApolloLayout

访问方式:登录 Apollo 后从左侧菜单「平台管理 → 审计日志」进入,或直接访问 /apollo/admin/audit-logs。相邻页(侧边栏顺序):项目管理 admin-projects.md(前,成员增删等写操作可到本页核对记录)、签名者白名单 admin-signers.md(后);对照概念:用户管理 admin-users.md(本页用户栏展示的就是用户名/user_id)、部署与漂移 deployments.md(发起部署的 POST /api/v1/deployments/start 会在本页出现,但 DEPLOYMENT_START 业务事件不在本页)。

2.2 认证与权限

2.3 端口与 API 前缀

直连调试提示:页面所有调用都走 apolloClient(带 Bearer token),经 Vite/网关的 /apollo-api 前缀分流到 18082 后端;脱离前端直调时用 http://127.0.0.1:18082/api/v1/audit-logs/api/v1/audit-logs/summary(同样需 audit:read 权限的登录 token,见 5.2)。/audit-logs/audit-logs/summary 在 gin 中是两条显式注册的精确路由,summary 不会被当作子路径参数吞掉,但注意路径要写全、不带尾部多余斜杠。

3. 界面布局

┌──────────────────────────────────────────────────────────────┐
│ Apollo 审计日志                                               │
│ [操作结果提示条(v-if alert.message,含「关闭」)]             │
├──────────────────────────────────────────────────────────────┤
│ ① 汇总卡片区(v-if summary):                                │
│    [日志总数] [近 24 小时] [按 action 计数卡片…(N 张)]       │
│ ② 筛选栏(card filter-bar,form):                           │
│    动作(action,精确匹配)[输入] 结果(result)[下拉]         │
│    起始时间(from)[datetime-local] 结束时间(to)[同左]       │
│    [查询] [重置]                                              │
│ ③ 日志表格(三态:加载中… / 空态 / 表格):                    │
│    时间 | 用户 | 方法 | 路径 | 状态码 | 结果 | 耗时(ms)        │
│    (行点击展开 → 详情行 colspan=7:action · resource ·        │
│     客户端 IP · user_agent · query_params/request_body/       │
│     error_message 三段深色代码区)                             │
│ ④ 分页:[上一页] 第 X / Y 页(共 N 条) [下一页]               │
└──────────────────────────────────────────────────────────────┘

各板块职责:

板块职责
页头 + 提示条标题「Apollo 审计日志」;提示条仅用于列表加载失败等错误提示(单槽,可「关闭」)
汇总卡片区展示 GET /audit-logs/summary 的日志总数 / 近 24 小时 / 按 action 计数
筛选栏四个筛选条件(action/result/from/to)+ 查询/重置;查询与重置都回到第 1 页
日志表格卡片加载中/空态/表格三态;行点击展开详情,行带 row-selected 高亮
详情展开行逐条展示 action、resource、客户端 IP、user_agent 与三段(已脱敏)代码区
分页区每页 20 条 id 倒序;上一页/下一页切换时保留当前筛选

布局要点:loading 只控制表格卡的三态切换,汇总卡与筛选栏不受影响——首次进入时表格卡显示 加载中...,汇总卡若已返回则照常渲染,两者进度独立。汇总数据未返回时整块卡片区不渲染(v-if summary),返回后「日志总数」「近 24 小时」两卡恒渲染,by_action 为空则不渲染任何 action 卡。表格三态互斥:加载中 > 空态 > 表格,只有「表格」态下分页区才出现。翻页/查询失败时 logs 保留旧数据、表格仍在,仅在顶部提示条报错,不把用户打回空态。整页不含弹窗、抽屉与模态框,全部交互收敛在筛选、表格、分页三块,键盘 Enter 在筛选表单内可提交查询(form 的 @submit.prevent)。

4. 交互元素

4.1 页头、操作结果提示条

控件位置含义必填/默认/可用条件操作效果触发后端调用边界与细节
页面标题页头 h2页面身份标识常驻Apollo 审计日志与源码 h2 逐字一致
操作结果提示条页头下方展示最近一次列表加载失败信息alert.message 才渲染;类型 alert-error展示 加载审计日志失败:{msg}页面唯一调用 showAlert 的路径是 fetchLogs 的 catch;汇总失败不弹提示(静默置空),见 4.2
提示条「关闭」提示条右侧清空当前提示提示条可见时可用alert.message='' 立即消失纯本地状态

页面状态组:logs(当前页日志数组)、total/page/pageSize(分页三件套,pageSize 固定 20)、loading(列表加载位,控制三态)、busy(按钮/翻页禁用位,见 8 章:声明后从未置 true)、alert(单槽提示)、summary(汇总对象,初始 null 不渲染卡片区)、expandedId(当前展开行 id,单开互斥)、filters(四个筛选条件对象)。totalPages 为计算属性 max(1, ceil(total/pageSize))

4.2 汇总卡片区

控件位置含义渲染条件数据来源边界与细节
「日志总数」卡汇总区第一张过滤口径下的总条数summary 非空summary.total ?? 0见「全局口径」备注
「近 24 小时」卡汇总区第二张近 24 小时内记录数同上summary.last_24h ?? 024 小时由后端按 timestamp >= now-24h 统计
action 计数卡其后按序排列每种 action 的记录数by_action 非空才渲染v-for (cnt, action) in summary.by_action,label 即 action 值key 为 'a' + action;action 长文案(如 POST /api/v1/deployments/start)会被整串当卡标题,word-break: break-all 折行
汇总加载失败---fetchSummary catch 里 summary.value = null静默降级:卡片区整块消失,无错误提示;列表仍正常

全局口径(重要)fetchSummary() 调用 /audit-logs/summary不携带任何筛选参数——即使你在筛选栏填了 action/result/from/to,汇总卡片的「日志总数/近 24 小时/按 action」仍是对全量日志的统计,与列表的过滤结果并不对应。点「查询/重置」时页面确实会重新调一次 summary 端点,但重新拉到的依旧是全局值,只是「看起来刷新了」。这是页面既有口径(前端缺陷候选见 8 章),理解后即可避免误读:要精确对比过滤口径下的数量,以列表页脚「共 N 条」为准。

4.3 筛选栏:动作与结果

控件位置含义默认操作效果触发后端调用边界与细节
动作(action,精确匹配)输入框筛选栏 form-group按 action 精确过滤空 = 不过滤输入即绑定 filters.action「查询」提交时作为 action 参数label 与 placeholder 逐字为源码原样;placeholder 如 DEPLOYMENT_START 与真实存储口径(METHOD + path)不一致,见 7 章/8 章;提交前 trim
结果(result)下拉筛选栏 form-group(select)按结果分类过滤"" = 全部选项 全部/success/failure/denied「查询」提交时作为 result 参数与后端三态归类一一对应;留空才发全部
「查询」筛选栏(btn-primary,type=submit)应用筛选:disabled="busy"applyFilter():页码回 1 → fetchLogs() + fetchSummary()GET /audit-logs + GET /audit-logs/summary表单 @submit.prevent 提交
「重置」筛选栏(btn-outline)清空筛选始终可点resetFilter():四个条件清空 → 页码回 1 → 重新拉列表与汇总同上与「查询」差异只在先清空 filters

动作筛选为服务端精确匹配:前端把 filters.action.trim() 作为 action 参数,后端 WHERE action = ? 全等比对——多一个空格、大小写不同都查不到。可参考汇总卡片 by_action 卡上显示的完整 action 值(如 POST /api/v1/projects)再回填输入框。

4.4 筛选栏:时间范围 from/to 的转换

控件位置含义默认操作效果触发后端调用边界与细节
起始时间(from)筛选栏 form-group(datetime-local)记录时间下界(含)空 = 不限绑定 filters.from「查询」时转 RFC3339 后作为 from 参数见下方时区换算
结束时间(to)筛选栏 form-group(datetime-local)记录时间上界(含)空 = 不限绑定 filters.to「查询」时转 RFC3339 后作为 to 参数后端 timestamp <= to 含端点

提交时经 toISO(v)new Date(v) 把 datetime-local 值(如 2026-09-07T08:00)按浏览器本地时区解释,再 toISOString() 转成 UTC 的 RFC3339 串(形如 2026-09-07T00:00:00.000Z)传给后端;非法输入解析成 NaN 时返回空串、不发该参数。后端 parseTimeRangetime.Parse(time.RFC3339, ...) 解析(解析失败视为未提供),最终落到 WHERE timestamp >= from AND timestamp <= to。日志表的 timestamp 存的是 UTC 时间,与 toISO 产出的 UTC 串同口径,故跨时区使用时:填「本地 8:00」实际过滤的是「UTC 0:00」开始的记录——这与 4.5 表格里时间列的展示口径(UTC 串截断展示、不转时区)是自洽的。

4.5 日志表格行与状态着色

表格卡片三态:loading 为真显示 加载中...logs.length===0 显示空态 暂无审计日志,请调整筛选条件。;否则渲染表格。列头依次 时间 / 用户 / 方法 / 路径 / 状态码 / 结果 / 耗时(ms)

内容边界与细节
时间formatTime(log.timestamp)RFC3339 串去 T、去 Z、截前 19 位展示(如 2026-09-07 13:50:00),不转浏览器时区;空值显示 -
用户log.username || log.user_id受保护端点展示登录用户名;公开端点(未过 Authn)两值皆空则整格为空
方法method-badge 徽标POST/GET,灰底黑字
路径<code class="cell-code">后端收到的原始路径(如 /api/v1/projects),等宽小字、可折行
状态码statusClass(log.status_code)≥500 红(st-5xx)、≥400 橙(st-4xx)、其余绿(st-2xx
结果status-badge + res-{result}success 绿、failure 红、denied 橙三色徽标
耗时(ms)log.duration_ms中间件计量的毫秒数,整数

行级交互:任意行 class="clickable-row"@click="toggleDetail(log.id)" 展开/收起该行;expandedId===log.id 时行加 row-selected#e8f0fe 背景)。单开互斥:点第二行会先把第一行收起,同一时刻只有一个详情行。展开详情时该行用 <template v-for> 渲染额外的 detail-rowcolspan=7),收起逻辑与项目页不同——本页不懒加载(数据随列表页一次带全,展开只做展示),翻页后详情自动消失。

4.6 详情展开行

详情行 detail-row 内含一个 .detail-panel,自上而下三部分:

区块内容逐字格式与细节
.detail-meta 元信息行灰字小号逐字:action={{ log.action }} · resource={{ log.resource_type }}/{{ log.resource_id }} · 客户端 IP {{ log.client_ip || '-' }} · user_agent {{ log.user_agent || '-' }}
查询参数块深色代码区子标题 查询参数(query_params,已脱敏);内容为脱敏后的原始 query 串,空则 (无)
请求体块深色代码区子标题 请求体(request_body,已脱敏);JSON/YAML/text 类请求体脱敏后原文,空则 (无)
错误信息块深色代码区子标题 错误信息(error_message);当前恒为空、显示 (无)(后端中间件未填充该列,见 8 章)

.code-block 为深色等宽代码区(white-space: pre-wrap + word-break: break-all,长 JSON 自动折行)。resource 字段的拆解:resource_type 由后端从路径首段提取(如 /api/v1/projectsprojects),resource_id 取**路由首个 :id 参数**(如 /api/v1/projects/3/members/9 记录为 projects/3,取的是项目 id 3 而非成员 id 9);无 :id 参数的路由 resource_id 为空串,界面上呈现 projects/ 尾带空段的形态。client_ipc.ClientIP() 值,user_agent 为浏览器 UA;两者空时分别显示 -

4.7 分页与数据加载细节

控件位置可用条件操作效果边界与细节
「上一页」分页区左(btn-sm btn-outline)page>1!busychangePage(page-1) 重新拉列表第 1 页禁用
页码文本分页区中-第 {{ page }} / {{ totalPages }} 页(共 {{ total }} 条)totalPages = max(1, ceil(total/pageSize)),total 为空态时不渲染分页区
「下一页」分页区右(btn-sm btn-outline)page<totalPages!busychangePage(page+1) 重新拉列表末页禁用

fetchLogs() 的请求参数组装:{page, page_size:20} 恒带;filters.action.trim() 非空追加 actionfilters.result 非空追加 resulttoISO(from/to) 非空才追加 from/to。成功后 logs=data.itemstotal=data.total;失败提示 加载审计日志失败:{msg}errMsg() 链见 5.1)且 loading 复位。挂载顺序(onMounted):fetchLogs()fetchSummary() 并行发起,互不依赖——列表失败只弹提示、汇总照常渲染;汇总失败静默置空、列表照常。翻页(changePage)只重拉列表不重拉汇总;查询/重置才同时拉两者。busy 本意是防连点(查询/翻页按钮共用),但全页从未把它置 true——按钮实际恒可点,连点会并发发多个同参 GET(幂等无害),见 8 章。

4.8 数据口径与格式化细节

本页几处展示口径易被误读,集中说明如下:

5. 后端关联

5.1 API 客户端

action/web/src/api/apolloClient.js:axios 实例 baseURL /apollo-api/v1、timeout 30000ms、请求拦截器对所有请求附加 Authorization: Bearer <token>(token 取 apollo_token,无则回退 aip_token)。响应拦截器:HTTP 2xx 且 code===0 时把 response.data 解包为业务数据(页面 const { data } = ... 直接得到业务对象);HTTP 2xx 但 code!==0 拒绝;4xx/5xx 提取 body.message || body.error 并把 message 别名写入 error.response.data.error 后 reject——**401 清 token 并跳 /apollo/login(登录页自身 401 不跳),403 alert('无权限执行该操作')**。页面 errMsg() 依次取 err.response.data.messageerr.response.data.errorerr.message未知错误

5.2 端点表

方法路径权限点请求参数(Query)页面触发点
GET/audit-logsaudit:readpage/page_size/action/result/from/to(额外支持但页面未用的 user_id/username/resource_type/project_id初次加载、查询/重置、翻页
GET/audit-logs/summaryaudit:read无(页面恒不带参,全局口径)初次加载、查询/重置

注册位置:action/products/apollo/server/server.go protected 组 835-836 行,均标注 access.RequirePerm(..., access.PermAuditRead)。handler 在 handlers.gohandleListAuditLogs(1748-1756 行)经 auditFilterFromQuery(1728 行)构造过滤、parsePage 解析分页后调 accessSvc.ListAuditLogshandleAuditSummary(1759-1766 行)同参数调 accessSvc.AuditSummary。分页实现:handler 层 parsePage 默认 page=1page_size=20上限 100(api.go 166 行);service 层 ListAuditLogs 再兜底一次默认 20、上限 500(access_service.go 443-451 行,防御纵深)。

5.3 响应结构示例

**GET /audit-logs?page=1&page_size=20(envelope 解包后为分页对象,单项即 ApolloAuditLog 行)**:

{
  "items": [
    {
      "id": 42,
      "timestamp": "2026-09-07T13:50:00.123456+08:00",
      "user_id": "2a3f9b7c-1c6e-4d4a-8f20-3e5c2d1a0b99",
      "username": "admin",
      "client_ip": "127.0.0.1",
      "user_agent": "Mozilla/5.0 ... Chrome/120.0",
      "method": "POST",
      "path": "/api/v1/projects",
      "query_params": "",
      "request_body": "{\"name\":\"core-pay\",\"description\":\"核心支付域\"}",
      "status_code": 201,
      "action": "POST /api/v1/projects",
      "resource_type": "projects",
      "resource_id": "",
      "project_id": 0,
      "result": "success",
      "error_message": "",
      "duration_ms": 38
    }
  ],
  "total": 128,
  "page": 1,
  "page_size": 20
}

字段含义(ApolloAuditLogaccess/models.go 72-91 行):id 自增主键(列表按 id DESC 倒序);timestamp 请求开始时间(落库时 autoCreateTime);user_id/username 登录主体(Authn 注入;公开端点为两空串);client_ip/user_agent 客户端信息;method/path HTTP 方法与原始路径;query_params 脱敏后的查询串(无则空串);request_body 脱敏后的请求体(无则空串);status_code 响应状态码;action 方法 + 空格 + 路径resource_type/resource_id 资源定位(见 4.6);project_id 列存在但 HTTP 中间件不写(恒 0,页面也无此筛选);result 三态归类;error_message 列存在但 HTTP 中间件不写(恒空);duration_ms 请求耗时。表名固定 apollo_audit_logs(models.go 94 行)。

**GET /audit-logs/summary(envelope 解包后,页面恒不带参)**:

{
  "total": 128,
  "by_result": { "success": 100, "denied": 18, "failure": 10 },
  "by_action": {
    "POST /api/v1/auth/login": 40,
    "GET /api/v1/projects": 22,
    "GET /api/v1/audit-logs": 6
  },
  "last_24h": 12
}

字段含义(AuditSummaryaccess/access_service.go 52-58 行):total 过滤口径总数;by_result 按结果分组计数;by_action 按 action 分组计数(页面把每一项渲染成一张卡);last_24h 近 24 小时条数(timestamp >= now-24h)。汇总端点的 by_result 前端不直接展示(卡片区只渲染 total/last_24h/by_action 三类),结果分布要人工从列表/徽标观察。

5.4 关键机制

HTTP 审计中间件(TAD-11)。注册在根路由组、位于 requestIDMiddleware 之后(server.go 681-685 行),c.Next() 前记录开始时间并按需采集请求体,之后读取响应状态组装条目调 AuditService.Record 异步落库。三个要点:① 跳过清单为探针与文档路径 /health/api/health/api/v1/docs(http_audit.go 66 行),其余全部 API 都记;② 请求体采集只看内容类型——auditableContentTypes 白名单(application/jsonapplication/yamltext/yamltext/plainapplication/x-yaml 前缀)命中才 io.LimitReader 读入并还原 Body 供后续 handler 解析,multipart 上传/二进制下载不采集,避免记录二进制噪音;读入上限受 maxBodyBytes(1MB,server.go 50 行)约束;③ **用户信息在 c.Next() 后读**:受保护端点由 access.Authn 注入 user_id/username,公开端点取不到则为空串——这就是 4.5「用户」列对登录类请求为空的原因。

结果三态归类auditResultForStatus)。>=500 → failure401/403 → denied(认证失败与权限不足单独一类);>=400 → failure(其余 4xx,含 404/409/422 等全部算失败);其余 → success。注意这里的 failure 是宽口径:业务上「正常被拒的 404/冲突 409」在审计里也归 failure,要看语义差异需结合 status_code 列。

脱敏与截断RedactText/RedactQuery/RedactBody)。落库前对请求体/查询串统一脱敏:超过 2048 字节先截到 2048 再拼 ...[truncated]truncateSuffix);再按正则把 JSON 键值对中 access_token/refresh_token/client_secret/password/authorization/api_key/apikey/signature/secret/token/key(长键名在前避免 token 截断 access_token)的值整体替换为 ***;明文 Bearer <token> 同样替换为 ***。查询串走 RedactQuery:解析出 password/token/access_token/refresh_token/secret/key/api_key/apikey/signature/authorization 参数即把值置 ***url.Values.Encode 会把 * 编成 %2A,落库前还原为 ***)。因此详情行看到的 body/query 是脱敏后内容,原文不可逆

异步落库(channel + worker)。AuditService 构造时启动 workerCount 个 worker goroutine 消费容量 1000 的缓冲 channel(audit.go 57 行);Record 用 select 投递,channel 满或 ctx 取消时落入 default 分支降级同步落库(不阻塞请求主路径)。所以「刚操作完立刻刷新本页」存在 worker 消费延迟(秒级),测试里也按此轮询等待记录出现。

筛选与分页auditFilterFromQuery 从 query 提取 user_id/username/action/result/resource_type/project_id(project_id 需能解析成 uint 否则忽略)与 parseTimeRange 解析的 from/to(RFC3339);applyAuditFilter 逐字段 WHERE 精确比对(action/result/resource_type 全等,timestamp >= fromtimestamp <= to)。前端只用了 action/result/from/to 四个,其余为直调 API 才可用的扩展能力。列表 Order("id DESC") 倒序——最新记录永远在列表最前,翻页是往更早的历史翻。

action 的存储口径与业务审计的隔离。中间件写 Action: c.Request.Method + " " + c.Request.URL.Path,即每条记录的 action 恒为「方法 + 空格 + 路径」(如 POST /api/v1/deployments/startGET /api/v1/projects/3/members)。与此并行的另一条业务审计链是 recordAudit(handlers.go 1780-1789 行):各业务 handler 在生命周期节点调用它,经 s.auditService.LogEventRef(...)product="apollo" 写入平台审计服务的哈希链业务表(AuditTableName 规则 = <product>_audit_log,即 apollo_audit_log 单数表),事件码形如 DEPLOYMENT_START/DEPLOYMENT_SYNC/DRIFT_DETECTED。本页查询的 ApolloAuditLogapollo_audit_logs 复数表)由 HTTP 中间件独占写入,两表不相交——因此在页面上按业务事件码过滤永远查不到记录,只有把 action 输入框填成真实的方法+路径才能命中。

**resource_type/resource_id 的提取规则**。auditResourceType/api/v1/ 前缀剥离后取路径首段(/api/v1/projects/3/artifactsprojects,无前缀路径 → other);resource_id 读路由参数 :id(首个匹配即命中),嵌套路由只记最外层资源 id。这解释了为何详情行 resource 粒度是「模块级 + 外层 id」,而非精确到被操作的子资源。

打开本页即触发自记录请求链。页面 onMounted 并行发 GET /audit-logsGET /audit-logs/summary,这两笔请求又各自产生一条审计(resource_type 均为 audit-logs);点「查询」再各加一条、翻页再加一条。因此列表头部时常出现自己的访问痕迹,summary 的 by_action 也会统计到 GET /api/v1/audit-logs——这不是泄漏,而是中间件「除探针与文档外全部记录」的必然结果。空库首屏:后端刚启动、尚未有任何 API 调用时,列表为空态、汇总卡全 0;第一个请求(往往是登录)落下后即有数据。慢接口归因提醒duration_ms 计的是中间件包住的整个请求周期(含 handler 执行与响应序列化),不区分服务端计算与网络传输;对经代理转发到 18082 的调用来说,该值约等于服务端视角耗时,可作慢接口初筛,但不能据此断定瓶颈在后端内部。

6. 权限与安全

7. 常见问题与排错

以下现象均由 AuditLogsPage.vue 的提示/渲染分支、后端中间件与 service 代码以及既有测试归纳,均可按步骤复现。

1. 现象:刚执行的操作在日志里查不到。 原因:审计为异步落库(channel + worker 消费),存在秒级延迟窗口;且若操作本身是业务生命周期事件(走 apollo_audit_log 单数表)而非 HTTP 调用,本页就永远不会有。处理:稍等 1-2 秒点「查询」重拉;确认你查的是 HTTP 动作(如 POST /api/v1/projects)而非 DEPLOYMENT_START 这类事件码。

2. 现象:action 输入框填「DEPLOYMENT_START」查不到任何记录。 原因:placeholder 示例沿用了业务事件码,但本页存储的 action 恒为「HTTP 方法 + 空格 + 路径」,精确匹配必然落空。处理:改填方法+路径,如 POST /api/v1/deployments/start;不确定时先看汇总卡片 by_action 卡上实际出现的 action 值再回填。

3. 现象:某次被拒操作显示为 denied,但另一类 4xx 显示为 failure。 原因:结果归类是服务端固定规则——401/403 → denied,其余 4xx(404/409/422 等)与 5xx → failure。处理:按此口径理解徽标;要区分具体失败原因以「状态码」列数值为准。

4. 现象:列表过滤出了结果,但顶部「日志总数」卡与实际不符。 原因:fetchSummary() 不携带筛选参数,汇总卡片恒为全量统计(全局口径),与过滤后的列表无对应关系。处理:以列表分页区「共 N 条」作为当前筛选口径下的准确计数;卡片只用于看全貌。

5. **现象:详情行请求体/查询串里的敏感值显示为 ***。** 原因:服务端 RedactBody/RedactQuery 落库前脱敏,属预期行为而非数据缺失。处理:如需原文只能到后端原始请求侧核对,页面无法恢复。

6. 现象:某行「用户」列为空。 原因:该请求落在公开端点(登录/注册/Agent pull/report)——未过 access.Authnusername/user_id 都是空串。处理:以 method/path 列判断是哪个公开调用;对登录失败类排查请结合状态码 401/denied。

7. **现象:展开行 error_message 恒显示 (无)。** 原因:ApolloAuditLog.ErrorMessage 列当前没有任何代码写入(HTTP 中间件不填),页面该区块形同预留。处理:把该区块视为占位;失败原因看状态码列与 path 列即可(见 8 章)。

8. 现象:点「查询/翻页」偶尔感觉没反应或列表闪一下又回旧数据。 原因:busy 全页从未置 true,按钮恒可点,连点会并发发出多个同参 GET——后完成的响应覆盖先完成的,属无副作用的竞态而非丢数据。处理:等列表加载完再点;刷新后如数据不一致以最后一次请求为准(见 8 章)。

9. **现象:本页自身的 GET /api/v1/audit-logs 出现在列表里。** 原因:HTTP 审计覆盖全部 API(探针/文档除外),查看审计页这个动作本身也被记录。处理:正常现象;按 action 过滤该值时能看到每 20 条一页的自我访问痕迹。

10. 现象:想按用户或资源类型筛。 原因:页面筛选栏只暴露 action/result/from/to 四项,后端支持的 user_id/username/resource_type/project_id 参数在页面没有入口。处理:用 curl 直调 GET /api/v1/audit-logs?resource_type=security-gate&result=denied(带 Bearer token 与 audit:read 权限)实现扩展过滤;或等前端补筛选控件。

11. 现象:想拿某条记录的 request_id 去后端日志对照,但审计行里找不到。 原因:审计模型没有持久化 request_id(envelope/响应头里的 X-Request-ID 仅当次请求有效,随记录不落库)。处理:改用「timestamp + username + method + path」四元组在表内近似定位;同一时刻并发同路径请求无法精确区分(见 8 章)。

12. 现象:后端刚启动,本页打开列表为空、汇总卡全 0。 原因:还没有任何 API 请求触发中间件写入(登录页都未访问过)。处理:先在其他页面发起一次带认证的操作(或直接登录一次),再回来查询;POST /api/v1/auth/login 是最容易产生的第一条记录。若操作后仍为空,检查 worker 消费延迟(1-2 秒后重拉)与数据库表是否存在(服务启动 AutoMigrate 会自动建表)。

8. 已知缺陷与边界

缺陷/边界说明
action 占位符误导输入框 placeholder「如 DEPLOYMENT_START」是业务事件码示例,与真实存储口径(METHOD + path)不一致,按它填永远查不到。web/src 可修:改 placeholder 为 POST /api/v1/projects 类示例
汇总卡恒全局口径fetchSummary() 不带筛选参数,action/result/from/to 只作用于列表;查询/重置虽重拉 summary 但仍是全量值,卡片数字与过滤结果可能差异显著
busy 形同虚设busy=ref(false) 声明后从未被置 true,「查询/翻页」禁用态恒不触发,连点会并发同参 GET(幂等、仅响应覆盖竞态)
error_message 恒空ApolloAuditLog.ErrorMessage 列无写入方(HTTP 中间件不填),详情行「错误信息」区块恒为 (无),前端预留了展示但没有数据
project_id 恒 0模型含 project_id 列且筛选层支持,但 HTTP 中间件不填充、页面无入口,按项目隔离查询当前不可用
时间展示 UTC 截断时间列与 detail 无时区换算,跨时区用户看到的时间与本地有偏差(口径与筛选一致:后端按 UTC 存取)
result=denied 的语义并集denied 同时覆盖 401(登录/凭据/ApiKey 无效)与 403(权限不足)及 Agent 出向鉴权失败,需结合 path 甄别类型,无细分维度
by_result 不展示summary 返回的 by_result 分布未渲染为任何卡片/图表,结果构成只能人工从徽标归纳
无导出/详情页日志只能页内翻看,无 CSV 导出与单条详情独立页;数据量大时按时间窗分段查看
异步延迟窗口审计异步落库,紧接操作后查询存在秒级空窗;测试/取证场景需等待 worker 消费
匿名流量混入公开端点(pull/report/login/register)请求也会落库且用户列为空,噪声需要按 path 自行过滤
无自动刷新页面仅在挂载/查询/重置/翻页时拉数据,无定时轮询——长时间挂页不会自动出现新记录
request_id 不落库ApolloAuditLogrequest_id 列,与响应头 X-Request-ID 无法互查;同刻并发同路径请求难以精确关联
无保留期/自动清理日志表无内置 TTL 与归档任务,长期运行持续累积,膨胀需数据库侧人工处理
反代下 IP 失真client_ipc.ClientIP(),直连时等于远端地址;经反向代理/网关接入而未配置信任代理时读到的是代理地址

附录 A:速查——页面文案与关键常量清单

场景源码取值/文案(逐字)
页面标题Apollo 审计日志
汇总卡片标签日志总数 / 近 24 小时(action 卡以 action 值为标签)
筛选 label动作(action,精确匹配) / 结果(result) / 起始时间(from) / 结束时间(to)
action placeholder如 DEPLOYMENT_START
result 选项全部 / success / failure / denied
筛选按钮查询 / 重置
列表列头时间 / 用户 / 方法 / 路径 / 状态码 / 结果 / 耗时(ms)
列表加载态加载中...
列表空态暂无审计日志,请调整筛选条件。
详情元信息行action={{ action }} · resource={{ resource_type }}/{{ resource_id }} · 客户端 IP {{ client_ip || '-' }} · user_agent {{ user_agent || '-' }}
详情子标题查询参数(query_params,已脱敏) / 请求体(request_body,已脱敏) / 错误信息(error_message)
详情空值(无)
分页文案第 {{ page }} / {{ totalPages }} 页(共 {{ total }} 条)
分页按钮上一页 / 下一页
加载失败提示加载审计日志失败:{msg}
提示条关闭关闭
result 取值success / failure / denied(徽标 res-success / res-failure / res-denied
状态码着色st-2xx 绿 / st-4xx 橙 / st-5xx
权限点audit:read(列表与汇总共用)
存储表apollo_audit_logs(HTTP 审计,本页数据源)
业务审计表apollo_audit_log(平台哈希链业务审计,本页不查)