第2章 审计日志基础知识:日志级别、时间戳、事件类型、用户标识、源IP等关键字段
做审计日志,最怕什么?
最怕你拿到一堆日志,却读不懂里面的信息。或者更糟——关键字段丢了,出了事根本查不出来。
我在早期的一个项目中,就因为少记了一个字段,导致安全排查时花了三天才定位到问题。从那以后,我对日志字段的设计就格外较真。
这一章,我们来拆解审计日志的六个核心字段。说白了,它们是日志的骨架。骨架搭不好,后面再怎么装饰都没用。
2.1 日志级别:给每条日志一个身份标签
日志级别是什么?说白了,就是告诉运维和审计人员:「这条日志有多重要」。
我记得有一次线上事故,生产环境错误日志被大量DEBUG信息淹没了。排查时翻了几万条日志,才发现关键错误早就躺在那儿了。嗯,从那以后,我对日志级别的定义就严格了许多。
常见的审计日志级别,我建议按这个标准来划分:
| 级别 | 含义 | 审计场景举例 | 使用建议 |
|---|---|---|---|
| DEBUG | 调试信息,开发用 | 函数调用链、内部变量值 | 仅开发环境开启,生产环境必须关闭 |
| INFO | 常规操作记录 | 用户登录、登出、密码修改 | 日常审计的主要信息来源 |
| WARN | 潜在风险或异常行为 | 连续登录失败、权限降级 | 需要人工关注,但不一定是故障 |
| ERROR | 操作失败或系统异常 | 数据写入失败、认证拒绝 | 必须告警,纳入监控 |
| CRITICAL | 严重安全事件 | 数据泄露、越权访问 | 实时告警、上报安全中心 |
2.2 时间戳:审计的命脉
没有时间戳的日志,等于没有灵魂。
时间戳是审计链中最重要的字段。为什么?因为你要靠它还原事件的时间线。你想想看,如果日志里只记录了「用户A删除了文件」,但没有时间是几点几分,你根本没法排查。
时间戳的设计,有几个硬性要求:
- 必须带时区。 我建议统一用UTC。我遇到过跨国项目,一个在东八区一个在零时区,日志比对的时候时区不一致,差点把排查方向带偏了。
- 精度至少到毫秒。 高并发场景下,秒级精度会导致日志时序混乱。比如同一秒内发生200次操作,你分不清先后。
- 单调递增。 不能用系统时间直接生成。防止时间回拨(NTP同步时常见)导致日志时间倒流。我习惯用单调时钟+系统时间双重标记。
一个标准的时间戳字段示例:
{
"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, 电信" // 可选,但强烈推荐
}
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"
}
嗯,这个结构看起来就很「审计」了。字段完整、清晰、可追溯。这就是我们后面所有章节的基础。
好了,这章就到这里。六个核心字段讲完了:日志级别、时间戳、事件类型、用户标识、源IP、操作对象 + 结果状态。记好它们,你的审计日志就不会太差。