审计日志的数据结构:字段、时间戳、源、目标、行为、结果

好,咱们开始啃第三块硬骨头。

上一章讲了日志收集的几种模式,但收上来的日志长什么样?如果每套系统的日志格式都随心所欲,那审计基本没法干。我在刚入行那会儿,接过一个客户的活,他们十几套系统的日志字段对不上,硬生生花了三周做字段映射。从那以后,我养成了一个习惯:先定标准,再谈分析。

核心六元组:一条日志的骨架

一条合格的审计日志,说白了就是由六个固定字段组成的「事件快照」。你想想看,你在监控系统里看到一条告警,你最想知道什么?谁干的、在哪儿干的、干了什么、结果如何、什么时候干的。这五个问题,正好对应五个核心字段:时间戳、源、目标、行为、结果。再加上一个用于归类的日志类型,就成了我现在要讲的「六元组」。

字段 英文/别名 必填 典型内容(举例)
时间戳 Timestamp 是 2025-04-01T14:23:10.882Z
源 Source / Actor 是 user:zhangsan / ip:192.168.1.10
目标 Target / Object 是 file:/data/report.pdf / db:orders
行为 Action / Operation 是 DELETE / DOWNLOAD / LOGIN
结果 Result / Status 是 SUCCESS / FAILURE / DENIED
日志类型 EventType / Category 强烈建议 ACCESS / AUTH / CONFIG

核心原则:六元组缺一个,这条日志的「证据链」就断了一环。我见过不少厂商的日志只记了IP和操作,压根没写是哪个用户,这等于白记。

时间戳:一切事件的锚点

时间戳是日志里最容易被忽视,但坑最深的一个字段。为什么?

精度问题。秒级精度在高并发场景下根本不够用。我记得有一次排查一个微服务的调用链,调用量每秒上千次,结果两条日志的时间戳一模一样,根本分不清先后顺序。后来我们统一把时间戳改成了毫秒级。相信我,如果你的系统吞吐量很高,直接用微秒或纳秒,别犹豫。

时区问题。这是老生常谈的坑了。我曾经吃过一个大亏:收集来的日志,有的用UTC,有的用了GMT+8,还有的干脆没带时区信息。排障的时候对着日志傻眼,以为事件发生在凌晨三点,其实是下午三点。我的建议是——日志里统一用UTC+ISO 8601格式,显示的时候再转本地时间。

// 推荐格式(ISO 8601)
2025-04-01T14:23:10.882Z // UTC,带时区Z
2025-04-01T22:23:10.882+08:00 // 东八区,偏移量

// 不推荐格式
2025-04-01 14:23:10 // 既无Z也无偏移,完全没法判断时区

避坑指南:我曾经在项目中见过设备时间偏差达到7分钟的情况,排查了一圈发现是设备NTP服务挂了没人管。记住:没有对齐的时间戳,审计日志就是一堆废纸。

源与目标:谁对谁做了什么

「源」是动作的发起方,可能是用户、进程、IP、服务账号。「目标」是动作的承受方,可能是文件、数据库、API端点、配置项。这两个字段必须充分描述,不能只写一个ID。我个人的习惯是,源字段至少包含用户标识+来源IP,目标字段至少包含资源类型+资源路径。

举个实际的例子,登录失败事件:

// 不够完整的源/目标
"subject": "user_001"
"object": "portal"

// 我建议的写法
"subject": { "username": "user_001", "ip": "10.0.0.5", "type": "human" }
"object":   { "resource": "portal", "id": "svc_portal_01", "type": "application" }

你想想看,如果只有个user_001,事后你怎么确定是谁在什么地点操作的?把信息写全,后续做用户行为分析时,连ETL清洗的功夫都省了。

行为与结果:动词要规范,状态要明确

行为(Action)应该是一个严格的动词枚举,而不是自由文本。我见过的反面教材:一条日志写的action是「用户操作」,另一条写的是「修改配置」,还有一条写的是「不小心点了删除」。这种日志在报表聚合时根本没法用。

建议的做法:定义一套统一的动作词表。比如:

  • 访问类:LOGIN, LOGOUT, READ, DOWNLOAD
  • 变更类:CREATE, UPDATE, DELETE, UPLOAD
  • 权限类:GRANT, REVOKE, ADD_USER, REMOVE_USER

结果(Result)我这里要特别说一句。很多人只记成功,不记失败,这是大忌。你想想看,一个攻击者试了100次密码,99次失败了,第1次成功了。如果你只记录了那1次成功登录,那攻击者的扫描行为就完全隐身了。所以,结果字段必须包含三种状态:SUCCESS / FAILURE / DENIED。DENIED是系统主动拒绝,FAILURE是操作执行失败了(比如数据库写入异常)。

小技巧:在结果里带一个错误码或具体原因。比如FAILURE后面跟个 err:"WRONG_PASSWORD",比光写个FAILURE有用得多。我习惯把错误原因字段叫 reasonCode 或 failCause。

扩展字段与元数据

六元组是骨架,但真正让日志「丰满」起来的是扩展字段。比如:

  • 会话ID(SessionId):能把同一次登录到登出的所有操作串起来。
  • 请求ID(RequestId / TraceId):分布式系统中的全链路追踪利器。
  • 原始日志(RawLog):有时候标准化字段丢失了上下文,保留原始文本可以回溯。

我自己在写日志规范时,会把字段分为三组:必填(六元组)、建议(会话ID、请求ID)、可选(负载数据、原始日志)。

知识体系:六元组的关系

下面这张图展示了审计日志核心字段的关联方式。每个审计事件都是一个六元组的实例,缺一不可。你可以把它当作一个审计事件的「DNA」。

审计日志六元组知识体系 审计事件 ⏱ 时间戳 精度/时区/格式标准化 👤 源 (Source) 用户/IP/服务/进程 🎯 目标 (Target) 文件/DB/API/配置项 ⚡ 行为 (Action) 动词枚举/规范命名 ✔️ 结果 (Result) 成功/失败/拒绝+错误码 📂 日志类型 访问/认证/配置变更 🧩 扩展字段 会话ID/请求ID/原始日志 六元组缺一不可

一个完整的日志示例

上面讲了不少理论,我们来拼一个完整的样子。假设用户zhangsan从IP 10.0.0.5通过SSH登录了服务器web-01,结果成功。那么标准化的审计日志大概长这样:

{
    "timestamp": "2025-04-01T14:23:10.882Z",
    "source": {
        "user": "zhangsan",
        "ip": "10.0.0.5",
        "type": "human",
        "sessionId": "abc123"
    },
    "target": {
        "resource": "server",
        "name": "web-01",
        "type": "host"
    },
    "action": "LOGIN",
    "result": "SUCCESS",
    "eventType": "AUTH",
    "metadata": {
        "protocol": "SSH",
        "port": 22
    }
}

重点回顾:不同场景下的「源」和「目标」会变化。比如写文件操作,源是进程或用户,目标是文件。API调用时,源是调用方IP,目标是端点URL。理解这个映射关系,无论面对什么系统,你都能快速拆解出其六元组。

如何定义你自己的日志规范

每个公司的基础设施不一样,但我建议你遵循这样一个流程来制定规范:

  1. 盘点所有日志源:应用、数据库、网络设备、云服务,一个都不能漏。
  2. 统一字段名称:不要一个系统叫user,另一个叫username,第三个叫subject。我见过最夸张的,一个日志里同时出现了user和actor两个字段指代不同用户,直接看懵了。
  3. 定义枚举值范围:Action和Result用有限的词表,不要自由书写。
  4. 增加上下文:比如带上请求ID、客户端版本、用户代理。
  5. 写一份规范文档:发布给所有开发团队,做成一个在线字典,随时查。

一个建议:别一次性把所有字段定死。我习惯的做法是先定六元组为核心,第一版只要求这六个字段必须存在。等大家习惯了,再慢慢加扩展字段。步子太大容易扯着蛋。

嗯,这一章内容就到这儿。数据结构的规范,是整个审计体系的奠基石。下一节我们会把这个标准套用到实际场景中,看看如何从零开始搭建一套合规的审计日志采集系统。

(本章结束)