第2章 审计日志基础知识:日志级别、时间戳、事件类型、用户标识、源IP等关键字段

做审计日志,最怕什么?

最怕你拿到一堆日志,却读不懂里面的信息。或者更糟——关键字段丢了,出了事根本查不出来。

我在早期的一个项目中,就因为少记了一个字段,导致安全排查时花了三天才定位到问题。从那以后,我对日志字段的设计就格外较真。

这一章,我们来拆解审计日志的六个核心字段。说白了,它们是日志的骨架。骨架搭不好,后面再怎么装饰都没用。

审计日志核心字段结构图 审计日志关键字段 日志级别 DEBUG / INFO / WARN / ERROR 时间戳 带时区 / 高精度 / 单调递增 事件类型 CREATE / READ / UPDATE / DELETE 用户标识 UID / 角色 / 会话ID 源IP地址 IP / 端口 / 代理链 操作对象 资源ID / 类型 / 变化前/后 必修字段组合:日志级别 + 时间戳 + 事件类型 + 用户标识 + 源IP + 操作对象 缺一不可,否则审计链不完整,无法满足合规要求

2.1 日志级别:给每条日志一个身份标签

日志级别是什么?说白了,就是告诉运维和审计人员:「这条日志有多重要」。

我记得有一次线上事故,生产环境错误日志被大量DEBUG信息淹没了。排查时翻了几万条日志,才发现关键错误早就躺在那儿了。嗯,从那以后,我对日志级别的定义就严格了许多。

常见的审计日志级别,我建议按这个标准来划分:

级别 含义 审计场景举例 使用建议
DEBUG 调试信息,开发用 函数调用链、内部变量值 仅开发环境开启,生产环境必须关闭
INFO 常规操作记录 用户登录、登出、密码修改 日常审计的主要信息来源
WARN 潜在风险或异常行为 连续登录失败、权限降级 需要人工关注,但不一定是故障
ERROR 操作失败或系统异常 数据写入失败、认证拒绝 必须告警,纳入监控
CRITICAL 严重安全事件 数据泄露、越权访问 实时告警、上报安全中心
我的一点小建议: 审计日志最少用三个级别就够了——INFO、WARN、ERROR。DEBUG太多,CRITICAL太频繁。我见过一些团队把CRITICAL当ERROR用,结果真正出事时,没人看日志了。

2.2 时间戳:审计的命脉

没有时间戳的日志,等于没有灵魂。

时间戳是审计链中最重要的字段。为什么?因为你要靠它还原事件的时间线。你想想看,如果日志里只记录了「用户A删除了文件」,但没有时间是几点几分,你根本没法排查。

时间戳的设计,有几个硬性要求:

  • 必须带时区。 我建议统一用UTC。我遇到过跨国项目,一个在东八区一个在零时区,日志比对的时候时区不一致,差点把排查方向带偏了。
  • 精度至少到毫秒。 高并发场景下,秒级精度会导致日志时序混乱。比如同一秒内发生200次操作,你分不清先后。
  • 单调递增。 不能用系统时间直接生成。防止时间回拨(NTP同步时常见)导致日志时间倒流。我习惯用单调时钟+系统时间双重标记。
避坑指南: 我曾经在一个项目中看到,日志时间戳用的是客户端本地时间。结果用户的电脑时间比服务器快了5分钟,审计链全乱了。后来我强制所有日志时间戳都在服务端生成,客户端只记录“请求发起时间”作为辅助字段。

一个标准的时间戳字段示例:

{
  "timestamp": "2025-06-15T14:23:19.847Z",
  "server_time_ns": 1718461399847000000
}

前者给人看,后者给机器排序用。两个都不丢。

2.3 事件类型:C.R.U.D. 之外的考量

事件类型描述的是「发生了什么」。

大部分审计场景都围绕C.R.U.D.展开——创建(Create)、读取(Read)、更新(Update)、删除(Delete)。但仅此还不够。你还得考虑一些特殊操作:

  • LOGIN / LOGOUT —— 认证事件
  • EXPORT —— 数据导出,这是安全审计重点
  • GRANT / REVOKE —— 权限变更
  • CONFIG_CHANGE —— 配置修改

事件类型建议用枚举值,不要用自由文本。为什么?因为查询审计日志时,你要能按事件类型快速过滤。自由文本会导致「DELETE」、「delete」、「删除」等各种写法,检索效率极低。

// 事件类型枚举示例(TypeScript)
enum AuditEventType {
  CREATE = "CREATE",
  READ = "READ",
  UPDATE = "UPDATE",
  DELETE = "DELETE",
  LOGIN = "LOGIN",
  LOGOUT = "LOGOUT",
  EXPORT = "EXPORT",
  GRANT = "GRANT",
  REVOKE = "REVOKE",
  CONFIG_CHANGE = "CONFIG_CHANGE",
}
要点: 事件类型必须做分类分层。例如:
1级:安全事件、业务事件、系统事件
2级:认证、授权、数据操作、配置变更
3级:具体的C.R.U.D.动作
这样在做审计分析时,可以按需聚合。

2.4 用户标识:谁干的?

用户标识回答的是「who」的问题。

很多人觉得这个简单——不就是用户ID吗?但我在项目中踩过坑:

  • 系统管理员操作时,用户ID可能是system或admin,但后台定时任务呢?该记录成scheduler还是root?
  • 第三方API调用时,用户标识是还是调用方的最终用户?
  • 批量操作涉及多个用户,该记录操作者还是被操作者?

我建议用户标识字段至少包含:

  • 操作者ID —— 谁发起的操作
  • 操作者角色 —— admin / user / service / system
  • 会话ID —— 关联当前登录会话,便于追踪完整操作链
  • 认证方式 —— password / OAuth / 证书 / 匿名
{
  "actor_id": "u_10086",
  "actor_role": "admin",
  "session_id": "sess_abc123",
  "auth_method": "password"
}

你想想看,如果只记了用户ID,但不知道他的角色,审计时你能判断他是否有权限做这个操作吗?不能。所以用户标识一定要带着上下文信息。

2.5 源IP地址:从哪里来?

源IP记录的是「从哪里发起的请求」。

但这里有个坑——很多应用都部署在负载均衡或反向代理后面。源IP变成了代理的IP,真正的客户端IP被丢掉了。我曾经排查一个安全事件,日志里全是内网IP,真正的攻击者IP完全找不到。后来我强制要求:

  • 记录客户端真实IP(通过X-Forwarded-For或X-Real-IP头获取)
  • 记录代理链(中间经过的所有代理IP)
  • 记录请求来源端口(便于关联TCP连接)
{
  "client_ip": "203.0.113.42",
  "x_forwarded_for": "203.0.113.42, 10.0.1.1, 10.0.2.1",
  "source_port": 54321,
  "geo_info": "CN, Shanghai, 电信"  // 可选,但强烈推荐
}
提示: IP地址不要只存IPv4,现在IPv6越来越普及。我的做法是统一存储字符串,不做转换,避免信息丢失。

2.6 其他关键字段:操作对象与结果状态

除了上面五个,还有两个字段我几乎每个审计日志都会带:

  • 操作对象(resource):被操作的具体资源。比如「删除文件」,操作对象是文件的路径和ID。格式建议:resource_type:resource_id,例如 file:doc_2025_report.pdf。
  • 结果状态(status):操作是否成功。二选一:SUCCESS 或 FAILURE。失败时还要带上 error_code 和 error_message,方便排查。

2.7 完整的审计日志示例

把上面所有字段拼起来,一条完整的审计日志大概长这样:

{
  "log_level": "INFO",
  "timestamp": "2025-06-15T14:23:19.847Z",
  "event_type": "DELETE",
  "actor_id": "u_10086",
  "actor_role": "admin",
  "session_id": "sess_abc123",
  "client_ip": "203.0.113.42",
  "source_port": 54321,
  "resource_type": "file",
  "resource_id": "doc_2025_report.pdf",
  "status": "SUCCESS",
  "message": "Admin user u_10086 deleted file doc_2025_report.pdf"
}

嗯,这个结构看起来就很「审计」了。字段完整、清晰、可追溯。这就是我们后面所有章节的基础。

硬性规则: 以上每个字段都不能为null或空字符串。我见过有人图省事,srcIP不记,事件类型不记。结果出了事,审计日志等于废纸。记住——审计日志不是给你看的,是给未来的你(或者法务、合规)看的。缺一个字段,可能就缺了关键证据。

好了,这章就到这里。六个核心字段讲完了:日志级别、时间戳、事件类型、用户标识、源IP、操作对象 + 结果状态。记好它们,你的审计日志就不会太差。