第3章 审计数据模型设计
审计数据模型是整个链路的地基。地基没打牢,后面跑起来全是坑。我个人习惯在设计阶段就把字段定义、日志结构、规范约束和扩展性一次性想清楚,否则后期改模型比重新写还痛苦。好,咱们直接开整。
3.1 审计事件的核心字段定义
一个审计事件,说白了就是“谁、在什么时候、对哪个资源、做了什么、结果怎样”。这五个要素缺一不可。我见过很多团队把字段砍得太干净,最后排查问题时恨不得时光倒流。
核心字段清单(必选)
| 字段名 | 类型 | 说明 | 举例 |
|---|---|---|---|
event_id | UUID | 全局唯一标识,用于去重和关联 | a1b2c3d4-... |
timestamp | ISO8601 | 事件发生时刻,精确到毫秒 | 2025-04-08T14:30:00.123Z |
actor_id | 字符串 | 操作主体标识(用户ID或服务名) | user_10086 |
action | 枚举 | 操作类型:CREATE / UPDATE / DELETE / READ | UPDATE |
resource_type | 字符串 | 资源类别,如 order / user / config | order |
resource_id | 字符串 | 资源主键 | ORD_2025040810 |
result | 枚举 | SUCCESS / FAILURE / PARTIAL | SUCCESS |
source_ip | 字符串 | 请求来源IP | 10.0.1.2 |
经验之谈:event_id 我建议用 UUIDv7,带时间戳前缀,既唯一又方便排序。曾经有个项目用自增 ID,结果跨库同步时天天打架。
3.2 审计日志的结构设计
光有字段不够,你得把它们组织成一条能落地、能解析的日志。我推荐使用 JSON 格式,原因很简单——扩展性好,解析快,序列化/反序列化生态成熟。你想想看,用 XML 或自定义二进制格式,光是写解析器就得浪费多少时间。
下面是我在项目中常用的一种结构,分了三层:
- 外层(元数据层):包含 event_id、timestamp、schema_version。用于路由和校验。
- 中间层(核心载荷):actor、action、resource、result。这是审计的最小语义集合。
- 内层(上下文扩展):包括请求参数、响应摘要、环境标签、调用链追踪信息。这层是可选的,但强烈建议保留。
注意:千万不要把敏感数据(密码、、完整业务数据)直接塞进审计日志。我曾经见过一个团队把用户银行卡明文写进去了,结果审计日志本身成了安全漏洞。
{
"event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2025-04-08T14:30:00.123Z",
"schema_version": "1.0",
"actor": {
"type": "user",
"id": "u_10086",
"name": "张三"
},
"action": "UPDATE",
"resource": {
"type": "order",
"id": "ORD_2025040810",
"attributes": {
"status": "paid"
}
},
"result": "SUCCESS",
"context": {
"source_ip": "10.0.1.2",
"trace_id": "trace_abc123",
"client_version": "2.3.1",
"request_id": "req_456"
}
}
3.3 规范与命名约定
没规矩不成方圆。审计日志如果各字段命名随心所欲,到最后就是个数据沼泽。我分享一下自己踩过的坑和最终沉淀下来的习惯。
| 规范项 | 推荐方案 | 不推荐的写法 |
|---|---|---|
| 命名风格 | 小写蛇形(actor_id) | 驼峰、全大写、混用 |
| 时间格式 | ISO8601 带时区 Z | 时间戳整数、本地时间无时区 |
| 枚举值 | 全大写常量(SUCCESS) | 大小写不一致(success / Success) |
| 字段顺序 | 固定顺序:元数据→核心→上下文 | 随机排列 |
| 可选字段 | 显式标为 null,而非缺键 | 动态省略字段 |
为什么推荐蛇形命名? 因为大部分日志解析工具(Elasticsearch、ClickHouse)对下划线友好,且不用额外转义。驼峰在 JSON 里看着还行,但写 SQL 时你就难受了。
3.4 扩展性设计
业务是活的,审计字段不能死。今天也许只记录订单状态,明天就要记录审批流程、操作原因、关联单据。怎么办?
我的答案是:预留扩展点,而不是提前把所有字段全列上。扩展点主要有三种方式:
- 字段追加:在
context对象里自由加 key-value,但不影响核心结构。 - 版本化:
schema_version字段标明日志格式版本,下游解析器按版本路由。升版本时保证向后兼容。 - 自定义标签:用
tags数组存储业务标签(如["finance", "compliance"]),便于后续按标签聚合。
我记得有个项目初期只设计了5个字段,后来因为合规要求,要加“数据脱敏标识”和“审批人”。因为我们提前留了 context 和 tags,直接往里塞,零 schema 变更,前后端都没动。
3.5 整体数据模型结构图
下面这张图把刚才讲的字段定义、分层结构、扩展关系串在一起。你看一眼就能明白审计事件的完整骨架。我把必选字段、可选字段和扩展字段用不同颜色区分。
3.6 我的实践建议
- 先定义必选字段:把刚说的五个要素变成 JSON Schema,用
required关键字钉死。少一个字段直接拒收。 - 日志尽量打在业务行为边界:比如下单成功后打,而不是在每个方法里打。否则审计日志量会爆炸,我见过一个服务一天打了两亿条审计日志,ES 直接挂了。
- 保留 raw_request 摘要:如果涉及修改操作,在
context里放一份请求参数的摘要(比如只保留关键参数,去掉 body 全文),方便后续定位。 - 不要过度设计:一开始不要追求“万能审计模型”,那只会让你陷入抽象陷阱。从最刚需的字段开始,快速上线,再根据审计需求迭代。
一个小技巧:把 schema_version 定义为整数,版本升级时只递增不改。这样下游解析器可以按版本判断字段是否存在,不需要频繁升级。我曾经用这个方法在三年内零中断地改了七次模型。
好了,审计数据模型的设计思路讲到这。核心就是:字段定义要精准,日志结构要清晰,规范要统一,扩展点要预留。拿到这个模型,下一步就是考虑怎么把日志高效地采集和传输过去。不过那是另一章的事了,咱们先把地基夯实。