3. 审计日志格式与常见类型(syslog、应用日志等)

做审计日志巡检,第一件事是什么?不是写脚本,是搞清楚日志长什么样。你想想,如果连字段都认不全,那脚本跑出来的结果你敢信吗?

我个人习惯拿到一台机器 先看 `/var/log/` 下有什么文件,然后 `head -5` 瞄一眼结构。日志格式说白了就几大类,但每一类里的坑都不少。

3.1 Syslog:老而弥坚的系统日志标准

Syslog 是从 Unix 时代就有的协议,至今仍是系统日志的基石。大部分 Linux 发行版里,`/var/log/syslog` 或 `/var/log/messages` 用的就是它。

标准 syslog 格式(RFC 3164)长这样:

<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8

拆开看:尖括号里的数字是优先级编码(设施×8 + 级别),后面是时间戳、主机名、进程名和消息。我刚开始接触时总搞不清那个数字怎么算,后来写了个小函数才记住。

新版的 RFC 5424 增加了结构化数据,格式更规整:

<14>1 2024-10-11T22:14:15.003Z mymachine.example.com su 1234 ID47 [exampleSDID@32473 iut="3" eventSource="Application"] BOM'su root' failed for lonvick

嗯,这里要注意:很多老设备还走旧版格式。你的巡检脚本如果只按新版解析,遇到旧版就全废了。

特性RFC 3164(旧版)RFC 5424(新版)
时间戳Oct 11 22:14:15(无年份)2024-10-11T22:14:15.003Z(含时区)
优先级尖括号内数字同上,但可省略
结构化数据无[exampleSDID@32473 …] 键值对
设备兼容几乎所有网络设备较新设备及软件
我曾经踩过的坑: 某次对接交换机日志,设备送的是 RFC 3164 但时间戳没有年份。脚本按当前年份补,结果跨年时把 12 月 31 日的日志标成了下一年的,导致归档错乱。后来我改为从日志文件名或系统时间推算窗口,不再硬补。

3.2 应用日志:百花齐放,但总有规律

应用日志就没那么统一了。每个中间件、每个框架都有自己的脾气。不过万变不离其宗,常见格式也就那么几种。

  • Web 服务器(Apache / Nginx):最流行的组合日志(Combined Log Format),一行一个请求。
  • Java 应用(Log4j / Logback):常用 PatternLayout,可自定义,但多数包含时间、级别、线程、Logger、消息。
  • JSON 日志:近年趋势,结构化程度最高。每条日志用大括号包裹,键值对清晰。
  • 自定义文本日志:嗯,你说它乱吧,它又有固定的分隔符;你说它规范吧,字段排列经常变。我在一个运维项目里见过用 `||` 分隔的、用 `^A`(Control A)分隔的……

拿最常见的 Nginx 日志举例:

192.168.1.1 - - [11/Oct/2024:22:14:15 +0800] "GET /api/health HTTP/1.1" 200 1234 "-" "curl/7.68.0"

字段依次是:客户端IP、标识符(通常 `-`)、用户、时间、请求行、状态码、字节数、Referer、User-Agent。这个格式我敢打赌你见过,因为它太普遍了。

我的小习惯: 写巡检脚本时,我会先用一个参数让用户指定“日志格式模板”。比如 Nginx 用 `$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent`。这样脚本和日志格式解耦,换格式只需改模板,不用改代码逻辑。

3.3 常见字段与它们的“性格”

不管日志长什么样,总有一些核心字段会反复出现。我把它们归纳成三组:

字段组包含字段在审计中的意义
时间相关时间戳(精确到秒或毫秒)、时区排序、频率分析、异常检测的时间基线
身份相关IP、用户名、进程名、PID追溯责任人、定位异常源
行为相关操作、结果码、消息判断成功/失败、识别违规操作

例如,你在 SSH 登录日志里看到 `Failed password for root from 10.0.0.5 port 22`,就可以提取 `root`、`10.0.0.5`、`Failed` 三个关键信息。

3.4 解析策略:面对混乱,我怎么做?

日志格式再怎么变,解析思路是死的。我总结了三个步骤:

  1. 观察样本 —— 取 10~20 条日志,肉眼确认分隔符、固定位置、可变部分。
  2. 选择解析器 —— 结构化(JSON/XML)用 `jq`;带时间戳的文本用 `awk` + `date`;复杂混合型写 Python/Ruby 小脚本。
  3. 处理异常 —— 比如某行被截断了,或字段里出现了意料之外的分隔符。我总会加一个“错误行计数”和“样本存储”,便于事后排查。
# 示例:用 awk 提取 syslog 中的时间、主机和消息
awk '{
    # syslog 典型:月 日 时:分:秒 主机 进程[PID]: 消息
    # 我们取前三个字段作为时间,第四字段主机,从第五字段开始为消息
    timestamp = $1 " " $2 " " $3
    hostname  = $4
    message   = substr($0, index($0, $5))
    print timestamp, hostname, message
}' /var/log/syslog | head -5

当然,这只是一个极简示范。真实场景里我会用 `grep -E` 先过滤,再用 `sed` 或 `perl` 做精细切割。

核心观点: 日志解析的本质是把非结构化文本变成结构化记录。你之后的全部巡检逻辑——频率统计、异常检测、趋势分析——都建立在这个转化之上。格式识别错了,后面全是错的。

3.5 一张图看懂审计日志格式分类

下面这张图我把常见审计日志格式做了一个体系梳理。你可以用它作为自己巡检脚本的“格式速查”:

审计日志常见格式分类体系 审计日志 系统日志 Syslog 应用日志 安全审计日志 RFC 3164 RFC 5424 Web日志 JSON日志 自定义文本 auth.log secure 灰色线表示层级关系,不同色块对应不同日志类别 巡检脚本需根据来源选择对应的解析策略 实际环境中,一台服务器可能同时产生多种格式的日志, 脚本应具备按文件路径或日志头部关键字识别格式的能力。

3.6 解析时的几个注意事项

  • 时区统一:各日志可能用 UTC、本地时间、甚至不带时区。我建议巡检脚本强制将所有时间转为 UTC 存储,避免排序和比较出幺蛾子。
  • 跨行日志:比如 Java 的堆栈信息占了多行。你用 `awk` 按行处理就会断掉。我一般先用 `sed` 把换行逻辑合并——例如遇到不以时间戳开头的行,就追加到前一行末尾。
  • 编码问题:有些老应用输出 GBK 或 Latin-1。我的做法是在脚本头统一检测 BOM 或 `file -i`,发现非 UTF‑8 就 `iconv -f gbk -t utf8` 转一下。
一个小技巧: 写解析脚本时,先写一个“格式探测器”,用正则尝试匹配已知的几种常见格式。匹配上了就自动选择对应的解析函数。这样即使新加一种日志格式,也只需要增加一个匹配模板,不影响主流程。

搞清楚了日志格式,下一步就是把这些规则编进巡检脚本里了。格式越清晰,你的脚本越强健。别怕一开始多花时间观察样本——我见过太多人上来就写逻辑,结果被一行格式异常打回原形。你想想,是不是这个理?