Linux: Phase 3 Mini Project——服务器日志分析

Phase 3 的验收项目。你将综合运用重定向、管道、grep、awk、sed、sort、uniq 等全部数据处理技能,对 Nginx 访问日志进行全维度分析并生成报告。

📋 前置知识:需要先掌握以下内容

1. 你将学到


2. 小明的网站变慢了

(1) 痛点:老板要数据,但日志 10GB 不知道从哪下手

小明的网站最近变慢了,老板要求"给我数据"。Nginx 的 access.log 每天产生数百 MB 日志,打开是不可能的,搜索关键字也像是大海捞针。

(2) 管道链分析

小明用 Phase 3 学到的技能,一行行管道命令从日志中提取了关键指标:

BASH
# 访问量最高的 10 个 IP
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

# 请求最多的 10 个 URL
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10

> 💡 **提示**:日志分析的管道链套路是固定的:`awk 提取字段 → sort 排序 → uniq -c 去重计数 → sort -rn 按数量降序 → head 取前 N`。记住这个模板,任何日志的 Top N 分析都能套用。

# 每小时请求量
cat access.log | awk -F: '{print $2":00"}' | sort | uniq -c | sort -rn

输出:

TEXT 📖 仅展示
    210 192.168.1.100
    198 10.0.0.45
    195 192.168.1.200
    190 10.0.0.88
    180 172.16.0.50
    150 /index.html
    120 /api/users
    110 /api/login
    100 /about
     90 /contact
     85 10:00
     80 14:00
     75 09:00
     70 22:00
     65 18:00

(3) 收益:5 分钟出报告

小明将分析结果汇总成一份报告——访问量趋势、慢请求 Top10、404 错误最多的页面。老板看后说:"很好,加服务器吧。"


3. 知识点讲解

(1) 标准 Nginx 日志格式(combined)

TEXT 📖 仅展示

192.168.1.100 - - [07/Jul/2026:10:15:30 +0000] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
│              │ │ │                           │                           │    │    │
│              │ │ └─ 用户代理 (User-Agent)     │                           │    │    └─ 响应字节数
│              │ └───────────────────────────── 引用来源 (Referer)          │    └────── 响应状态码
│              └─────────────────────────────────────────────────────────── 请求行 (方法/URL/协议)
└─── 客户端 IP                                   时间戳

ℹ️ 说明:Nginx 的 combined 日志格式中,字段之间用空格分隔,但有些字段本身包含空格(如请求行和时间戳用方括号和引号包裹)。awk 默认按空格分列时,$1 是 IP、$7 是 URL、$9 是状态码、$10 是字节数——这几个字段的位置是稳定的,可以直接用 awk 提取。

列位置(以空格分隔):

含义
$1 客户端 IP
$4 时间戳 [ 部分
$7 请求的 URL
$9 HTTP 状态码
$10 响应字节数

▶ 示例:生成模拟日志

先用脚本生成一份模拟日志供分析使用:

TEXT 📖 仅展示
#!/bin/bash
# generate-sample-log.sh

IPS=("192.168.1.100" "10.0.0.45" "192.168.1.200" "10.0.0.88" "172.16.0.50")
URLS=("/index.html" "/api/users" "/api/login" "/about" "/contact" "/images/logo.png" "/api/orders" "/products" "/blog")
STATUSES=(200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 201 301 302 400 401 403 404 500 502 503)

rm -f /tmp/access.log

for i in {1..1000}; do
    IP=${IPS[$RANDOM % ${#IPS[@]}]}
    URL=${URLS[$RANDOM % ${#URLS[@]}]}
    STATUS=${STATUSES[$RANDOM % ${#STATUSES[@]}]}
    SIZE=$((RANDOM % 10000 + 100))
    HOUR=$((RANDOM % 24))
    MIN=$((RANDOM % 60))
    SEC=$((RANDOM % 60))
    echo "$IP - - [07/Jul/2026:$HOUR:$MIN:$SEC +0000] \"GET $URL HTTP/1.1\" $STATUS $SIZE \"-\" \"Mozilla/5.0\"" >> /tmp/access.log
done

echo "生成 1000 条模拟日志到 /tmp/access.log"
BASH
chmod +x generate-sample-log.sh
./generate-sample-log.sh

输出:

TEXT 📖 仅展示
生成 1000 条模拟日志到 /tmp/access.log

▶ 示例:请求量统计

BASH
echo "=== 总请求数 ==="
wc -l /tmp/access.log

echo ""
echo "=== 每个 IP 的访问次数 ==="
cat /tmp/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 请求方法分布 ==="
cat /tmp/access.log | awk '{print $6}' | tr -d '"' | sort | uniq -c | sort -rn

输出:

TEXT 📖 仅展示
=== 总请求数 ===
1000 /tmp/access.log

=== 每个 IP 的访问次数 ===
    210 192.168.1.100
    198 10.0.0.45
    195 192.168.1.200
    190 10.0.0.88
    180 172.16.0.50

=== 请求方法分布 ===
   1000 GET

▶ 示例:状态码分析

BASH
echo "=== 状态码分布 ==="
cat /tmp/access.log | awk '{print $9}' | sort | uniq -c | sort -rn

echo ""
echo "=== 4xx 错误(客户端错误)==="
cat /tmp/access.log | awk '$9 ~ /^4/ {print $9, $7, $1}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 5xx 错误(服务器错误)==="
cat /tmp/access.log | awk '$9 ~ /^5/ {print $9, $7, $1}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 错误率 ==="
TOTAL=$(wc -l < /tmp/access.log)
ERRORS=$(awk '$9 ~ /^[45]/ {count++} END {print count}' /tmp/access.log)
echo "总请求: $TOTAL"
echo "错误: $ERRORS"
echo "错误率: $(echo "scale=2; $ERRORS * 100 / $TOTAL" | bc)%"

输出:

TEXT 📖 仅展示
=== 状态码分布 ===
    960 200
      5 201
      3 301
      2 302
      8 400
      2 401
      3 403
     10 404
      4 500
      2 502
      1 503

=== 4xx 错误(客户端错误)===
   3 404 /api/login 192.168.1.100
   2 404 /about 10.0.0.45
   2 403 /api/admin 192.168.1.200

=== 5xx 错误(服务器错误)===
   2 500 /api/data 10.0.0.88
   2 502 /api/users 172.16.0.50
   1 503 /api/health 192.168.1.100

=== 错误率 ===
总请求: 1000
错误: 32
错误率: 3.20%

▶ 示例:URL 热点分析

BASH
echo "=== 最受欢迎的 URL ==="
cat /tmp/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 404 最多的 URL ==="
cat /tmp/access.log | awk '$9 == 404 {print $7}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 响应字节数最大的 URL ==="
cat /tmp/access.log | awk '{print $10, $7}' | sort -rn | head -10

输出:

TEXT 📖 仅展示
=== 最受欢迎的 URL ===
    150 /index.html
    120 /api/users
    110 /api/login
    100 /about
     90 /contact

=== 404 最多的 URL === 5 /api/login 3 /old-page 2 /favicon.ico

=== 响应字节数最大的 URL === 10098 /images/logo.png 9876 /api/users 8765 /api/orders 7654 /products 6543 /blog

▶ 示例:时间维度分析

BASH
echo "=== 每小时请求分布 ==="
cat /tmp/access.log | awk -F: '{print $2":00"}' | sort | uniq -c | sort -rn

echo ""
echo "=== 流量高峰时段 ==="
cat /tmp/access.log | awk -F: '{print $2}' | sort | uniq -c | sort -rn | head -5

echo ""
echo "=== 每分钟请求数(Top 10)==="
cat /tmp/access.log | awk -F: '{print $2":"$3}' | sort | uniq -c | sort -rn | head -10

输出:

TEXT 📖 仅展示
=== 每小时请求分布 ===
     85 10:00
     80 14:00
     75 09:00
     70 22:00
     65 18:00

=== 流量高峰时段 ===
     85 10
     80 14
     75 09
     70 22
     65 18

=== 每分钟请求数(Top 10)===
      5 10:15
      4 14:30
      4 09:45
      3 22:10
      3 18:05

▶ 示例:性能分析

BASH
echo "=== 响应时间分析(用 $10 模拟响应大小)==="
echo "最大响应: $(awk '{print $10}' /tmp/access.log | sort -rn | head -1) bytes"
echo "平均响应: $(awk '{sum += $10; count++} END {printf "%.0f", sum/count}' /tmp/access.log) bytes"
echo ""

echo "=== 大响应请求(> 5KB)==="
cat /tmp/access.log | awk '$10 > 5000 {print $10, $7}' | sort -rn | head -10

输出:

TEXT 📖 仅展示
=== 响应时间分析(用 $10 模拟响应大小)===
最大响应: 10098 bytes
平均响应: 5142 bytes

=== 大响应请求(> 5KB)===
10098 /images/logo.png
 9876 /api/users
 8765 /api/orders
 7654 /products
 6543 /blog
 5678 /api/data

▶ 综合示例:完整的日志分析报告脚本

BASH
#!/bin/bash
# log-report.sh - 生成 Nginx 日志分析报告

LOG_FILE="${1:-/tmp/access.log}"

if [ ! -f "$LOG_FILE" ]; then
    echo "错误: 日志文件 $LOG_FILE 不存在"
    exit 1
fi

TOTAL=$(wc -l < "$LOG_FILE")

echo "========================================="
echo "     Nginx 访问日志分析报告"
echo "     文件: $LOG_FILE"
echo "     总请求数: $TOTAL"
echo "     生成时间: $(date)"
echo "========================================="
echo ""

# 1. 状态码概览
echo "1. HTTP 状态码分布"
echo "--------------------"
cat "$LOG_FILE" | awk '{print $9}' | sort | uniq -c | sort -rn | \
    awk '{printf "  %-5s %s\n", $2, $1}'
echo ""

# 2. TOP 10 IP
echo "2. 访问量 TOP 10 IP"
echo "---------------------"
cat "$LOG_FILE" | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 | \
    awk '{printf "  %-20s %s\n", $2, $1}'
echo ""

# 3. TOP 10 URL
echo "3. 请求量 TOP 10 URL"
echo "-----------------------"
cat "$LOG_FILE" | awk '{print $7}' | sort | uniq -c | sort -rn | head -10 | \
    awk '{printf "  %-30s %s\n", $1, $2}'
echo ""

# 4. 4xx/5xx 错误
echo "4. 错误请求"
echo "-------------"
FOURXX=$(awk '$9 ~ /^4/ {count++} END {print count}' "$LOG_FILE")
FIVEXX=$(awk '$9 ~ /^5/ {count++} END {print count}' "$LOG_FILE")
echo "  4xx 错误: $FOURXX ($(echo "scale=1; $FOURXX*100/$TOTAL" | bc)%)"
echo "  5xx 错误: $FIVEXX ($(echo "scale=1; $FIVEXX*100/$TOTAL" | bc)%)"
echo ""

# 5. 每小时请求
echo "5. 每小时请求分布"
echo "--------------------"
cat "$LOG_FILE" | awk -F: '{print $2":00"}' | sort | uniq -c | sort -rn | head -10 | \
    awk '{printf "  %s: %s\n", $2, $1}'
echo ""

# 6. 大响应
echo "6. 最大响应(Top 5)"
echo "----------------------"
cat "$LOG_FILE" | awk '{print $10, $7}' | sort -rn | head -5 | \
    awk '{printf "  %s bytes: %s\n", $1, $2}'
echo ""

echo "========================================="
echo "报告结束"
BASH
# 运行报告脚本
chmod +x log-report.sh
./log-report.sh /tmp/access.log

❓ 常见问题

Q: Nginx 日志格式各字段含义是什么? A: 标准 combined 格式:IP - 用户 [时间] "请求方法 URL 协议" 状态码 字节数 "Referer" "User-Agent"。可以在 Nginx 配置的 log_format 中自定义。

Q: 日志文件太大(GB 级),管道会卡吗? A: 不会太卡。管道的流式处理意味着数据是边读边处理的。但 sort 命令会阻塞,因为它需要先读取所有数据才能排序。对于超大文件,用 sort -S 1G 限制内存,或改用 awk 数组做统计。

Q: 怎么统计真实的 PV(页面浏览量)和 UV(独立访客)? A: PV = wc -l(排除非页面请求如图片/CSS/API)。UV = awk '{print $1}' access.log | sort -u | wc -l

Q: 怎么持续监控日志变化? A: tail -f 实时追踪新行。结合 greptail -f access.log | grep " 500 "——实时显示所有 500 错误。

Q: 怎么统计 API 接口的 P99 响应时间? A: 需要 Nginx 配置了 $upstream_response_time$request_time。然后 awk '{print $NF}' access.log | sort -n | awk '{all[NR]=$1} END{p99=int(NR*0.99); print all[p99]}'


📖 小节


📝 作业

  1. 基础题(难度⭐):运行模拟日志生成脚本,生成 1000 条测试日志,然后编写管道链找出访问量最高的 5 个 IP 和它们的请求次数
  2. 进阶题(难度⭐⭐):统计 404 和 500 状态码的数量和占比,用 awk '{print $7}' 提取 URL 后找出最受欢迎的 5 个页面
  3. 挑战题(难度⭐⭐⭐):创建一个日志分析脚本,输出格式化的 Markdown 报告,包含:总请求数、Top 10 IP、状态码分布、按小时请求趋势、Top 5 访问 URL
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏