大规模解析 Web 与应用日志
使用 grep、cut 和 awk 从访问日志中提取状态码、延迟和客户端字段。
大规模解析 Web 与应用日志 是 CoddyKit 上的免费 DevOps Bootcamp 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 DevOps Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 DevOps Bootcamp 课程共包含 4 节课。
什么是 Web 访问日志
每个 HTTP 服务器——Apache、Nginx、Caddy——都会为每个请求向访问日志写入一行。理解这些行的结构,是进行所有日志分析工作的基础。
典型的组合日志格式(CLF)行如下:
- 客户端 IP——发出请求的客户端
- 时间戳——请求发生的时间
- 请求行——方法、路径和协议
- 状态代码——HTTP 响应(200、404、500……)
- 发送字节数——响应正文的大小
- 来源页——请求来源的页面
- 用户代理——浏览器或机器人标识字符串
来自 /var/log/nginx/access.log 的示例行:
192.168.1.10 - alice [11/Jun/2026:14:32:01 +0000] "GET /api/orders HTTP/1.1" 200 1482 "-" "curl/7.88.1"
在大规模环境中,这些文件每天会增长到数百万行。本课的目标是使用标准 BASH 工具,高效地从这些文件中提取、筛选和汇总字段。
使用 tail 和 grep 采样实时日志
在编写任何流水线之前,请先检查日志以了解其结构。tail 可以让您观察实时数据流;grep 则能立即将内容缩小到相关行。
常见模式:
tail -n 1000 access.log——最后 1000 行tail -f access.log——实时跟踪tail -f access.log | grep '" 5'——只显示到达时的 5xx 错误
关键在于,grep 会针对整行进行匹配,因此固定模式的起始位置很重要。匹配带空格的 ' 500 ' 可以避免误匹配 URL 路径中包含字符串 500 的情况。
#!/usr/bin/env bash
# Watch only HTTP 5xx errors arriving in real time
tail -f /var/log/nginx/access.log \
| grep --line-buffered '" 5[0-9][0-9] '使用 cut 提取状态代码
cut 按分隔符拆分每一行,并打印选定的字段。在组合日志格式中,按空格拆分时状态代码位于第 9 个字段;但由于请求行两侧有引号,因此从已知锚点开始计数会更安全。
一个可靠的方法是:由于请求行始终位于引号内,状态代码始终是请求字段结束引号之后的第一个标记。使用 cut -d'"' -f3 提取请求引号之后的全部内容,然后使用第二个 cut -d' ' -f2 选出状态代码。
这种两阶段 cut 是处理 CLF 日志的经典写法——速度快,而且不依赖外部组件。
#!/usr/bin/env bash
# Print only the HTTP status code from each log line
# Input format: ... "GET /path HTTP/1.1" 200 1482 ...
cut -d'"' -f3 /var/log/nginx/access.log \
| cut -d' ' -f2 \
| sort \
| uniq -c \
| sort -rn使用 awk 统计状态代码
awk 比 cut 更强大,因为它可以跨行累积状态。统计出现次数的惯用模式,是使用以目标值为键的关联数组。
在 CLF 中,字段 $9(从 1 开始编号,以空格分隔)就是状态代码。awk 会处理每一行、递增计数器,然后在 END 块中打印排序后的汇总结果。
为什么优先使用 awk,而不是 cut | sort | uniq -c?因为 awk 可以在单次遍历中完成处理,无需先对整个文件排序——当日志达到数百 GB 时,这一点至关重要。
#!/usr/bin/env bash
# Count HTTP status codes in a single awk pass
awk '{ count[$9]++ }
END {
for (status in count)
printf "%6d %s\n", count[status], status
}' /var/log/nginx/access.log \
| sort -rn筛选错误并提取客户端 IP
最常见的运维任务之一,是找出产生最多错误的客户端 IP。这需要将筛选(只保留错误行)和字段提取(提取第 1 个字段中的 IP)结合起来。
流水线策略:
- 使用
awk在一个步骤中按状态代码范围筛选并提取 IP——避免单独再运行一次grep - 将结果传给
sort | uniq -c | sort -rn | head,快速查看排名前 N 的结果
这种模式的速度足够快,可以在单台服务器上处理 10 GB 的日志文件,而无需将文件全部载入内存。
#!/usr/bin/env bash
# Top 10 IPs generating HTTP 4xx or 5xx errors
awk '$9 ~ /^[45][0-9][0-9]$/ { print $1 }' \
/var/log/nginx/access.log \
| sort \
| uniq -c \
| sort -rn \
| head -10从应用日志解析响应延迟
应用服务器(Rails、Gunicorn、使用 morgan 的 Express 等)通常会记录请求耗时。可以配置 Nginx,在每行末尾将 $request_time 作为额外字段输出。
nginx.conf 中的自定义 Nginx 日志格式示例:
log_format timed '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" rt=$request_time';
日志中包含延迟后,您就可以使用 awk 在无需将数据载入数据库的情况下,计算数百万个请求的平均值、最大值和百分位数近似值。
#!/usr/bin/env bash
# Compute average and max request_time from Nginx timed log
# Assumes last field is rt=<seconds> e.g. rt=0.042
awk '{
# Extract numeric value after rt=
n = split($NF, a, "=")
if (n == 2 && a[1] == "rt") {
t = a[2] + 0
sum += t
count++
if (t > max) max = t
}
}
END {
if (count > 0)
printf "Requests: %d Avg: %.4fs Max: %.4fs\n", count, sum/count, max
}' /var/log/nginx/timed_access.log使用 awk 构建延迟直方图
单个平均值会掩盖尾部延迟。直方图可以展示分布情况——例如大多数请求很快、少数请求非常慢(长尾),还是所有请求的分布比较均匀。
关键是使用 awk 内部的整数运算,将每个值归入经过四舍五入的区间。先乘以 1000(将秒转换为毫秒),再进行整数除法,即可得到整齐的区间边界。
这样会生成可以直接在终端中阅读的文本直方图;对于快速调查而言,这通常比将数据发送到 Grafana 更快。
#!/usr/bin/env bash
# Latency histogram (50ms buckets) from Nginx timed log
awk '{
n = split($NF, a, "=")
if (n == 2 && a[1] == "rt") {
ms = int(a[2] * 1000) # convert to ms
bucket = int(ms / 50) * 50 # round down to 50ms boundary
hist[bucket]++
}
}
END {
for (b in hist)
printf "%6dms %d\n", b, hist[b]
}' /var/log/nginx/timed_access.log \
| sort -n提取用户代理并检测机器人
用户代理字段(按 " 拆分时的第 6 个字段)用于标识客户端。爬虫、抓取程序和恶意机器人经常会污染指标并抬高错误计数。将它们筛除后,可以更清晰地了解真实用户流量。
常见的机器人标识:bot、crawler、spider、curl、python-requests、Googlebot、Bingbot。
使用 grep -iv(不区分大小写地反向匹配)排除已知机器人,或者使用 awk 按 " 拆分,并直接匹配 UA 字段。
#!/usr/bin/env bash
# Count top 15 User-Agent strings, excluding known bots
awk -F'"' '{ print $6 }' /var/log/nginx/access.log \
| grep -iv -e 'bot' -e 'crawler' -e 'spider' -e 'curl' \
-e 'python' -e 'wget' -e 'Go-http-client' \
| sort \
| uniq -c \
| sort -rn \
| head -15按端点汇总流量
了解哪些端点接收的流量最多,以及产生的错误最多,有助于确定优化和容量规划的优先级。请求路径位于带引号的请求字段中。
按 " 拆分,取第 2 个字段(请求行),然后去除方法和协议,以提取路径。对于包含路径参数的 API,例如 /users/12345,您还可以使用 sed 或更复杂的 awk 模式,将 ID规范化为 /users/:id。
#!/usr/bin/env bash
# Top 20 requested endpoints (method + path, no query string)
awk -F'"' '{ print $2 }' /var/log/nginx/access.log \
| awk '{ print $1, $2 }' \
| sed 's|/[0-9][0-9]*\b|/:id|g' \
| sort \
| uniq -c \
| sort -rn \
| head -20使用 awk 将错误与端点关联
最强大的单次遍历分析,会同时组合多个字段:端点、状态代码,以及可选的延迟。以组合值为键的 awk 关联数组,可以简洁而快速地完成这项工作。
下面的模式会在一次遍历中统计每个端点的 5xx 错误——无需临时文件,也无需中间排序,直到最后才排序。当您需要在大型日志上于一分钟内获得结果时,生产环境的可观测性脚本通常采用这种方法。
#!/usr/bin/env bash
# Count 5xx errors per endpoint path in a single pass
awk -F'"' '{
# $2 = request line e.g. "GET /api/orders HTTP/1.1"
# $0 in original space-split: $9 = status
split($0, fields, " ")
status = fields[9]
if (status ~ /^5/) {
split($2, req, " ")
path = req[2]
# Normalise numeric IDs
gsub(/\/[0-9]+/, "/:id", path)
errors[path]++
}
}
END {
for (p in errors)
printf "%6d %s\n", errors[p], p
}' /var/log/nginx/access.log \
| sort -rn \
| head -20处理轮换和压缩后的日志
大多数服务器每天都会轮换日志。较旧的文件会使用 gzip 压缩为 access.log.1.gz、access.log.2.gz 等。标准工具无法直接读取这些文件,但以下两种方法都很实用:
zcat——解压到标准输出,再传入流水线zgrep——无需解压,直接在 gzip 文件内部执行 grep
如果要分析包含未压缩文件和压缩文件的完整一周日志,可以使用进程替换,或使用 zcat 将它们连接起来。下面的代码片段会在一次 awk 调用中处理最近 7 个轮换文件和当前实时日志,无需临时文件。
#!/usr/bin/env bash
# Aggregate status codes across a week of rotated logs
# Handles both plain and gzip-compressed rotation files
LOG_DIR="/var/log/nginx"
{
cat "${LOG_DIR}/access.log" 2>/dev/null
zcat "${LOG_DIR}/access.log".*.gz 2>/dev/null
} | awk '
{ count[$9]++ }
END {
for (s in count)
printf "%6d %s\n", count[s], s
}' | sort -rn在组合日志格式中,哪个 awk 字段存储 HTTP 状态代码?
您正在编写一个 awk 单行命令,用于从采用 Combined Log Format(以空格分隔,请求行用引号括起)的标准 Nginx 访问日志中筛选出仅 HTTP 4xx 响应。哪个字段编号可以正确标识 HTTP 状态码?
课程回顾:日志分析流水线
在本课程中,您仅使用标准 BASH 工具,构建了一套完整的工具集,用于大规模分析 Web 和应用程序日志。
涵盖的关键技术:
- 先了解结构 — Combined Log Format 具有可预测的字段布局;了解这一点后,您就可以使用
cut -d'"'或awk字段引用进行可靠拆分。 - 提取状态码 —
awk '{ count[$9]++ }'可在一次遍历中统计所有状态码;使用$9 ~ /^5/可筛选服务器错误。 - 延迟分析 — 使用
awk解析rt=自定义字段,无需任何外部工具即可计算平均值、最大值和直方图分桶。 - 客户端和机器人分析 — 使用
-F'"'按"拆分,以访问 User-Agent 字段;通过管道传给grep -iv,排除机器人后再进行汇总。 - 端点规范化 — 在
awk中使用gsub(/\/[0-9]+/, "/:id"),在计数前合并带参数的路径。 - 轮换日志 — 在子 shell 中组合
cat和zcat,将所有轮换文件送入单次流水线处理。
这些模式可以组合使用:您可以在单个流水线中串联筛选、提取、规范化和汇总,在普通硬件上处理数亿行日志。掌握这些基本工具后,在临时事件调查中,您很少还需要专用的日志汇总服务。
常见问题解答
「大规模解析 Web 与应用日志」课时是免费的吗?
是的 — 「大规模解析 Web 与应用日志」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 DevOps Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 DevOps Bootcamp 课程共包含 4 节课。
「大规模解析 Web 与应用日志」这节课中我会学到什么?
使用 grep、cut 和 awk 从访问日志中提取状态码、延迟和客户端字段。 你通过在浏览器中直接运行的动手代码来练习 DevOps Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 DevOps Bootcamp 需要有经验吗?
无需任何先前经验。CoddyKit 上的 DevOps Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「大规模解析 Web 与应用日志」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 DevOps Bootcamp 课中编写并运行代码吗?
能。每节 DevOps Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 大规模解析 Web 与应用日志
- 实时跟踪日志与流式告警
- 在脚本中使用 journalctl 查询 journald
- 从日志流计算指标与直方图