4. 日志格式分析:三大主流日志的结构拆解

日志解析,说白了就是跟格式打交道。你格式拆不明白,后面写什么规则都是白搭。我这些年看过的日志格式少说也有上百种,但万变不离其宗——大部分都脱胎于三种基础类型:Web 访问日志、系统日志、应用日志。

今天咱们就把这三类日志的「骨架」拆开来看。你掌握了这个,以后遇到再奇怪的格式,也能一眼看出它的底子。

4.1 Web 访问日志:Apache 与 Nginx 的「标准答案」

Web 日志是日志解析里最常见的类型。我刚开始做运维那会儿,每天第一件事就是翻访问日志,看有没有异常请求。

4.1.1 Apache 的 Combined 格式

Apache 默认有两种格式:Common Log Format (CLF) 和 Combined Log Format。后者比前者多了 Referer 和 User-Agent,实战中基本都用 Combined。

一条典型的 Combined 日志长这样:

192.168.1.100 - frank [10/Oct/2023:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 2326 "https://example.com/home" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"

咱们把它拆开看:

字段 示例值 说明
远程主机 IP 192.168.1.100 客户端的 IP 地址
客户端身份 - identd 协议返回,通常为 -
认证用户 frank HTTP 认证的用户名,无认证则为 -
请求时间 [10/Oct/2023:13:55:36 +0800] 格式固定,时区信息在方括号内
请求行 "GET /api/users HTTP/1.1" 方法 + URI + 协议版本
状态码 200 HTTP 响应状态码
响应字节数 2326 不含响应头的 body 大小
Referer "https://example.com/home" 请求来源页面
User-Agent "Mozilla/5.0 ..." 客户端浏览器/工具标识
我的习惯:解析 Apache 日志时,我一般用空格做第一层分割,但时间字段和请求行都带引号或方括号,需要单独处理。直接用正则比 split 更稳。

4.1.2 Nginx 的默认格式

Nginx 的默认格式跟 Apache 的 Combined 几乎一样,但有个关键区别——时间格式。Nginx 的时间不带方括号,而且时区格式不同。

192.168.1.100 - frank [10/Oct/2023:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 2326 "https://example.com/home" "Mozilla/5.0"  # Apache

192.168.1.100 - frank 10/Oct/2023:13:55:36 +0800 "GET /api/users HTTP/1.1" 200 2326 "https://example.com/home" "Mozilla/5.0"  # Nginx

看出来了吗?Nginx 的时间字段外面没有方括号。这个细节坑过不少人——包括我。有一次我写了个解析脚本,直接套用 Apache 的格式,结果 Nginx 日志全解析失败,排查了半天才发现是方括号的问题。

4.1.3 自定义日志格式

生产环境里,很少有人用默认格式。我见过最夸张的一次,一个团队在日志里塞了 30 多个字段,包括上游响应时间、缓存命中状态、SSL 协议版本等等。

Nginx 自定义格式示例:

log_format extended '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time '
                    '$ssl_protocol $ssl_cipher';

对应的日志:

192.168.1.100 - - [10/Oct/2023:13:55:36 +0800] "POST /order HTTP/2.0" 201 128 "https://example.com/cart" "curl/7.68.0" 0.032 0.028 TLSv1.3 TLS_AES_256_GCM_SHA384
避坑指南:我曾经遇到过一个案例,自定义格式里字段之间用了多个空格,结果用 split() 解析时出现了空字段。我的建议是:无论自定义格式多复杂,字段分隔符一定要统一且唯一,否则解析规则会写得非常痛苦。

4.2 系统日志:Syslog 的「三要素」

系统日志跟 Web 日志完全是两套逻辑。Web 日志关注的是「谁、什么时候、干了什么」,而系统日志关注的是「哪个程序、什么级别、说了什么」。

4.2.1 Syslog 的标准结构

一条标准的 Syslog 包含三个核心部分:

<13>Oct 10 13:55:36 myhost sshd[1234]: Failed password for root from 192.168.1.100 port 22 ssh2

拆解如下:

  • 优先级(PRI):<13>,由设施(Facility)和级别(Severity)计算得出。13 = 1*8 + 5,即 Facility=1(user-level),Severity=5(notice)。
  • 时间戳:Oct 10 13:55:36,注意没有年份,这是 Syslog 的老传统。
  • 主机名:myhost,发送日志的设备名称。
  • 进程信息:sshd[1234],进程名和 PID。
  • 消息体:剩余部分,具体的事件描述。

4.2.2 Facility 与 Severity 的对应关系

这两个概念是 Syslog 的灵魂。Facility 告诉你是哪个子系统,Severity 告诉你有多严重。

Facility 值 关键字 说明
0 kern 内核消息
1 user 用户级消息
3 daemon 系统守护进程
4 auth 安全/认证消息
5 syslog syslog 内部消息
16-23 local0-local7 本地自定义使用
Severity 值 关键字 说明
0 emerg 系统不可用
1 alert 必须立即处理
2 crit 严重状态
3 err 错误
4 warning 警告
5 notice 正常但重要
6 info 信息
7 debug 调试

核心思路:解析 Syslog 时,我习惯先把 PRI 值拆出来,通过位运算拿到 Facility 和 Severity。这样后面做告警分级就方便了——比如 Severity 小于 3 的,直接进 PagerDuty。

4.2.3 现代 Syslog 的变体

嗯,这里要注意。传统的 Syslog 格式太老了,很多现代系统开始用 RFC 5424 格式。它长这样:

<13>1 2023-10-10T13:55:36.123Z myhost sshd 1234 - [timeQuality tzKnown="1" isSynced="1"] Failed password for root

区别很明显:时间用了 ISO 8601 格式,加了版本号,还多了结构化数据段。解析的时候,我建议直接用正则把 PRI、版本、时间、主机、进程、PID、消息体这几个字段分别提取出来。

4.3 应用日志:最「自由」也最「混乱」的类型

应用日志是三类日志里最让人头疼的。为什么?因为没有统一标准。每个开发团队都有自己的「审美」。

4.3.1 常见应用日志格式

我归纳了一下,应用日志大概有三种主流风格:

  • 纯文本风格:时间 + 级别 + 消息,简单粗暴。
  • JSON 风格:结构化,机器友好,但可读性差。
  • 混合风格:前面是固定字段,后面是自由文本。

举个例子:

# 纯文本
2023-10-10 13:55:36.123 ERROR [http-nio-8080-exec-1] com.example.OrderService: Failed to create order for user 12345

# JSON
{"timestamp":"2023-10-10T13:55:36.123Z","level":"ERROR","logger":"com.example.OrderService","thread":"http-nio-8080-exec-1","message":"Failed to create order","userId":12345,"duration":1523}

# 混合
2023-10-10 13:55:36.123 | ERROR | http-nio-8080-exec-1 | c.e.OrderService | Failed to create order | userId=12345 duration=1523ms

4.3.2 解析策略:先分类,再拆解

面对应用日志,我的策略很简单:先判断格式类型,再选择解析方式。

我的经验:如果日志是 JSON 格式,直接用 json.loads() 解析,又快又稳。如果是纯文本或混合格式,我会先看时间戳的位置和格式——它通常是第一个字段,也是最容易识别的。

4.4 三种日志的核心差异对比

咱们用一张表来总结:

维度 Web 访问日志 系统日志 (Syslog) 应用日志
标准化程度 高(Combined 格式为主) 中(RFC 3164/5424) 低(无统一标准)
核心字段 IP、时间、请求、状态码 PRI、时间、主机、进程 时间、级别、类名、消息
时间格式 [dd/MMM/yyyy:HH:mm:ss Z] MMM dd HH:mm:ss 或 ISO 8601 多种,常见 yyyy-MM-dd HH:mm:ss.SSS
解析难度 低(格式固定) 中(PRI 需要计算) 高(格式多变)
典型用途 流量分析、安全审计 系统监控、故障排查 业务分析、错误追踪

4.5 日志格式拆解的整体框架

说了这么多,咱们用一张图把整个知识体系串起来。这是我做日志解析时脑子里一直挂着的框架:

日志格式拆解核心框架 Web 访问日志 系统日志 (Syslog) 应用日志 Apache Combined / Common Nginx 默认 / 自定义 RFC 3164 (传统) RFC 5424 (结构化) 纯文本 / JSON / 混合 无统一标准 核心字段 IP · 时间 · 请求行 状态码 · 字节数 · UA 核心字段 PRI (Facility+Severity) 时间 · 主机 · 进程 · 消息 核心字段 时间 · 级别 · 类名 线程 · 消息 · 上下文 解析策略:先识别格式 → 再定位分隔符 → 最后提取字段

这张图我建议你存下来。每次拿到一种新日志,先往这个框架里套一套——属于哪类?什么格式?核心字段有哪些?解析策略怎么选?走完这三步,你的解析规则基本就成型了。

一句话总结:日志格式拆解的本质,就是找到「不变」的骨架,应对「万变」的细节。Web 日志看请求行,Syslog 看 PRI,应用日志看时间戳——抓住这三个锚点,解析规则就不会跑偏。