SaaS环境审计日志隔离方案 - 第2节
审计日志的分类
说起审计日志,我最早接触是在一个金融 SaaS 项目里。那会儿甲方要求所有用户行为都得留下痕迹,连翻看菜单都要记一笔。一开始觉得有点过度,后来出了个内部人员误删生产数据的事故,幸好日志完整,十分钟就定位到人。嗯,从那以后,我对审计日志再也不敢马虎了。
按用途区分四种核心类型
审计日志不是「一种」日志,它按照触发场景和记录目的可以分为四个大类。我在不同项目里见过五花八门的命名,但归根结底逃不出这几种:
| 类型 | 主要用途 | 典型事件举例 |
|---|---|---|
| 访问日志 | 记录谁在什么时间访问了什么资源 | 用户登录、API 调用、页面浏览 |
| 操作日志 | 记录具体的数据变更行为 | 创建/修改/删除订单、修改密码 |
| 异常日志 | 记录系统错误、业务校验失败等非预期情况 | 鉴权失败、请求超时、数据库连接池耗尽 |
| 安全事件日志 | 记录与安全直接相关的高风险行为 | 多次登录失败、权限提升、异常 IP 访问 |
你可能会问:操作日志和安全事件日志有什么区别?说白了,操作日志重在「改了什么」,安全事件日志重在「可疑行为」。比如一个普通用户改了自己的昵称,记一笔操作日志就够了;但同一 IP 半小时内改了 50 次昵称,那就要往安全事件日志里扔一条告警。
混用还是分开?
我建议在日志收集层就按类型打上 event_type 标签,写入同一套存储系统,但分别建索引或路由到不同的物理队列。这样可以兼顾查询效率和分析灵活度。我记得有个项目把所有日志一股脑写进同一个 Elasticsearch 索引,结果安全分析时被大量业务日志淹没,查找高危事件像大海捞针。后来改了分区策略才解决。
核心字段规范
字段规范是日志能被机器解析和人工排查的基础。我见过最惨的一次事故,是因为时间戳用了本地时间没带时区,跨区域排查时大家对着日志各说各的,整整浪费了半天。从那以后,我对字段规范的要求近乎偏执。
必选字段清单
以下字段我要求每个 SaaS 项目都必须有,缺一个就拒绝上线:
| 字段名 | 类型 | 说明与要求 |
|---|---|---|
timestamp |
ISO 8601 | 精确到毫秒,必须带时区(如 2025-03-17T14:23:11.123Z) |
user_id |
String | 用户唯一标识,不能为空;匿名请求用 anonymous |
event_type |
枚举 | access / operation / exception / security |
action |
String | 具体动作,推荐动词加名词组合,如 order.create |
source_ip |
IPv4/IPv6 | 请求来源 IP,若通过代理透传还需 x_forwarded_for |
resource |
String | 被操作的资源路径或标识,如 /api/v1/orders/12345 |
result |
String | 成功 / 失败 / 拒绝;失败时附带 error_code |
这些字段组合起来,基本能回答「谁在什么时候从哪做了什么,结果怎样」。
扩展字段与上下文
必选字段之外,我习惯再塞几个上下文属性,它们能大幅提升排查效率:
session_id—— 串联一次会话中的多次操作request_id—— 微服务间链路追踪user_agent—— 客户端类型识别payload_diff—— 操作日志里记录变更前后的值(敏感字段要脱敏)tenant_id—— 多租户场景下必备,否则没法做租户隔离查询
看一个实际的生产 JSON 日志片段:
{
"timestamp": "2025-03-17T08:12:34.567Z",
"user_id": "u_abc123",
"event_type": "operation",
"action": "order.update",
"source_ip": "203.0.113.42",
"resource": "/api/v1/orders/ORD-20250317-001",
"result": "success",
"tenant_id": "t_enterprise_01",
"request_id": "req-xyz789",
"payload_diff": {
"before": { "status": "pending", "amount": 199.00 },
"after": { "status": "paid", "amount": 199.00 }
}
}
你看,这个 payload_diff 直接告诉排查人员改了什么字段,不需要去数据库查 before image,省了不少功夫。
记录原则与最佳实践
记录原则听起来像老生常谈,但我踩过的坑告诉我,每一条都是用真金白银换来的。
完整性——不漏记
审计日志不能只记成功不求失败。我曾经接手过一个系统,登录失败日志默认被 try-catch 吞掉了,结果发生了暴力破解事件,日志里一条失败记录都没有,完全没法溯源。正确的做法是:在网关或统一拦截器里,不管业务返回什么,都把请求上下文先扔到日志管道。用个装饰器模式或者 AOP 就能做到。
不可篡改——防删改
日志记录之后,任何人都不能「悄悄」修改或删除它。怎么做?
- 日志写入后立即设置只读权限(操作系统级别或对象存储策略)。
- 使用写入日志文件后即时上传到远程不可变存储(如 S3 的 Object Lock)。
- 对关键日志行计算哈希后上链或存储到专门的审计数据库,定时校验完整性。
时序一致性——可回放
日志的时间戳必须单调递增,不能出现时间跳跃(除了时钟校正)。我建议所有日志都在服务端打时间戳,不要信任客户端的时钟。如果日志需要跨数据中心聚合,使用 NTP 同步,并且记录时带上时钟漂移偏移量,以便事后校准。
保留策略——存多久?
不同日志类型保留时间不同。合规要求通常强制至少保留 6 个月,金融类可能要求 2 年以上。我习惯的做法:
- 热索引(7 天):用于日常告警和实时分析,存 Elasticsearch。
- 温存储(1~3 个月):用便宜点儿的对象存储,配合 Presto 或 Athena 查询。
- 冷归档(1~3 年):压缩后存 Glacier,仅用于合规审计。
下面这张图可以帮你快速记住审计日志的核心结构:
三个模块互相支撑:日志类型决定字段模板,字段规范保证数据可解析,记录原则确保数据可信可用。你设计审计方案时,最好把这三个维度都列出来逐一检查。
好了,关于审计日志的基础分类、字段规范和记录原则,我们今天就聊到这。记住一点:日志写得好不好,直接决定了出事之后你是一脸懵还是胸有成竹。动手之前先把规范定死,后面省心无数。