2. 常见日志格式解析:Apache、Nginx、IIS、JSON与自定义格式
日志格式,说白了就是服务器告诉你「谁、什么时候、干了什么」的纸条。但不同服务器写的纸条,排版完全不同。
我个人习惯把日志格式分成两类:标准通用格式和自定义键值格式。前者以Apache/Nginx的CLF(Common Log Format)为代表,后者以JSON最为典型。你想想看,如果连原始日志都读不对,后面写的过滤规则全是白搭。
这一节我会带你逐个拆解。每种格式我都会给一个真实样本,再告诉你我实际项目中是怎么提取关键字段的。
2.1 Apache日志格式
Apache是最老牌的Web服务器。它的日志格式分两种:通用日志格式(CLF)和组合日志格式(Combined Log Format)。企业里大多用后者,因为它多记录了Referer和User-Agent——这两个字段在安全分析中非常关键。
先看一个Combined格式的样本:
192.168.1.101 - - [10/Mar/2024:08:12:35 +0800] "GET /api/user/info HTTP/1.1" 200 3245 "https://example.com/login" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
这个样本里,空格和引号都是有意义的。我教你一个快速拆解方法:
| 位置 | 字段 | 说明 |
|---|---|---|
| 1 | 远程IP | 客户端IP,这里是192.168.1.101 |
| 2~3 | identd、auth信息 | 通常是两个短横线 - -,表示无此信息 |
| […] | 时间戳 | 方括号内的日期时间,含时区 |
| "…" | 请求行 | 引号内包含方法、URI、协议版本 |
| 6 | 状态码 | 200、404、500等 |
| 7 | 响应字节数 | 不含HTTP头部的body大小 |
| "…" | Referer | 请求来源URL |
| "…" | User-Agent | 客户端标识,常见扫描器特征在此 |
2.2 Nginx日志格式
Nginx的日志格式和Apache类似,但写法更灵活。Nginx允许管理员通过log_format指令自定义字段顺序,所以你不能假设字段位置是固定的。
我见过最常见的企业配置是这样的:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
生成的日志样本:
192.168.1.101 - - [10/Mar/2024:08:12:35 +0800] "POST /api/login HTTP/1.1" 200 892 "https://example.com/" "Mozilla/5.0" "10.0.0.1, 172.16.0.2"
注意最后多了一个引号字段 $http_x_forwarded_for。这个字段在安全分析里特别重要,因为它记录了真实的客户端IP(经过代理链时)。我见过有团队只取remote_addr做过滤,结果被代理IP绕过了——这是一个典型的坑。
2.3 IIS日志格式
IIS的日志格式和前面两个完全不同。它使用空格分隔的字段名-值结构,头部有一行注释说明字段顺序。你看这个样本:
#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2024-03-10 08:12:35
#Fields: date time c-ip cs-uri-stem sc-status sc-bytes cs(User-Agent) cs(Referer)
2024-03-10 08:12:35 192.168.1.101 /api/user/info 200 3245 Mozilla/5.0+ https://example.com/
解析IIS日志的关键两步:
- 先读#Fields行,知道每个位置的字段名。
- 按字段名定位,而不是按位置硬编码。
我推荐的做法是:先过滤掉所有以#开头的注释行,再从#Fields行动态构建解析规则。你看,这样哪怕管理员改了字段顺序,代码也不用动。
2.4 JSON格式
JSON格式现在越来越普及。说白了,它就是一堆键值对,结构清晰,解析方便。一个典型的JSON格式日志长这样:
{
"timestamp": "2024-03-10T08:12:35.123Z",
"client_ip": "192.168.1.101",
"method": "GET",
"uri": "/api/user/info",
"status": 200,
"bytes_sent": 3245,
"referer": "https://example.com/login",
"user_agent": "Mozilla/5.0",
"x_forwarded_for": ["10.0.0.1", "172.16.0.2"],
"response_time_ms": 45
}
JSON日志的好处是字段名一目了然,而且支持嵌套结构。我在做规则匹配时,通常会:
- 扁平化:把嵌套的JSON拍平成一维结构。
- 索引关键字段:给client_ip、uri、user_agent建索引。
- 正则匹配:在uri和user_agent字段上做模式匹配。
2.5 自定义格式解析方法
有些系统不走标准格式,自己搞一套。比如某些反向代理或WAF设备,日志长这样:
[2024-03-10 08:12:35] IP=192.168.1.101 | METHOD=GET | URI=/api/user/info | CODE=200
这种格式没有统一的解析器可用。我的方法是三步走:
- 第一步:判断分隔符。这里是竖线
|。 - 第二步:识别字段标记。每个字段以
KEY=开头。 - 第三步:按标记提取值,忽略顺序。
我会写一个通用的行解析器:先按分隔符拆分,再逐段按=拆分键值对。这个方法可以覆盖90%的格式变种。剩下的10%?嗯,那得手写正则了。
=),如果直接按等号拆分,就会拆错。我习惯用左起第一个等号作为分隔,而不是最后一个。
2.6 格式解析的逻辑架构
下面这张图概括了五种日志格式的解析路径。你可以看到,不管原始格式是什么,最终都要提取出一组标准化的字段,才能写统一的过滤规则。
2.7 我的总结
解析日志格式这件事,看似枯燥,但它是安全过滤的基石。格式搞错了,过滤规则就是空中楼阁。
我个人的习惯是这样的:
- Apache/Nginx:优先用正则组,从请求行里拆出method和uri。
- IIS:一定先读#Fields行,再写解析器。
- JSON:扁平化后建索引,不要在原始嵌套结构上写正则。
- 自定义格式:先看分隔符,再看标记,最后做边界测试。