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 客户端标识,常见扫描器特征在此
我的经验:写Apache日志解析规则时,优先用正则捕获组。我习惯把请求行拆成method、uri、protocol三个子字段,这样方便后续按接口路径做白名单过滤。

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绕过了——这是一个典型的坑。

避坑指南:我曾经在处理一起爬虫攻击时,发现源IP全是内网地址。后来查了x_forwarded_for字段,才发现真实IP是外网的。从那以后,我只要做HTTP外发过滤,一定优先看x_forwarded_for里的最左端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日志的好处是字段名一目了然,而且支持嵌套结构。我在做规则匹配时,通常会:

  1. 扁平化:把嵌套的JSON拍平成一维结构。
  2. 索引关键字段:给client_ip、uri、user_agent建索引。
  3. 正则匹配:在uri和user_agent字段上做模式匹配。
一点建议:字段名最好统一用小写加下划线(snake_case)。我在一个项目里看到有人混用了clientIp、client_ip、ClientIP三种写法——那维护起来简直是灾难。

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%?嗯,那得手写正则了。

重要提醒:解析自定义格式时,一定要做边界测试。比如URI里可能包含等号(=),如果直接按等号拆分,就会拆错。我习惯用左起第一个等号作为分隔,而不是最后一个。

2.6 格式解析的逻辑架构

下面这张图概括了五种日志格式的解析路径。你可以看到,不管原始格式是什么,最终都要提取出一组标准化的字段,才能写统一的过滤规则。

日志格式解析路径 Apache格式 通用/组合日志格式 Nginx格式 自定义log_format IIS格式 W3C扩展格式 JSON格式 键值对/嵌套结构 自定义格式 分隔符+标记识别 标准化字段提取 IP、时间、方法、URI、状态码、UA、Referer 规则匹配引擎 白名单/黑名单/正则/威胁情报 告警/阻断/放行 Apache Nginx IIS JSON 自定义

2.7 我的总结

解析日志格式这件事,看似枯燥,但它是安全过滤的基石。格式搞错了,过滤规则就是空中楼阁。

我个人的习惯是这样的:

  • Apache/Nginx:优先用正则组,从请求行里拆出method和uri。
  • IIS:一定先读#Fields行,再写解析器。
  • JSON:扁平化后建索引,不要在原始嵌套结构上写正则。
  • 自定义格式:先看分隔符,再看标记,最后做边界测试。