Linux: フェーズ3ミニプロジェクト — サーバーログ分析
フェーズ3の総合プロジェクトです。リダイレクト、パイプ、grep、awk、sed、sort、uniqなどすべてのデータ処理スキルを組み合わせて、Nginxアクセスログの全次元分析を行いレポートを生成します。
📋 前提条件:まず以下を完了していること
- レッスン12:標準ストリームとリダイレクト
- レッスン13:パイプとフィルター
- レッスン14:テキスト検索
- レッスン15:テキスト処理三銃士
1. このレッスンで学ぶこと
- Nginxログフォーマットの解析
- リクエスト量の時間帯分析
- 人気URLランキング
- ステータスコード分布統計
- レスポンスタイムのパフォーマンス分析
2. 小明のWebサイトが遅くなった
(1) 悩み:ボスがデータを要求、10GBのログは閲覧不可能
小明のWebサイトが最近遅く、ボスは「データを出せ」と要求しました。Nginxのaccess.logは毎日数百MB生成 — 開くのは不可能、キーワード検索も干し草の山から針を探すようなもの。
(2) パイプラインチェーン分析
小明はフェーズ3のスキルを使い、パイプコマンドでキーメトリクスを順次抽出しました:
# Top 10 IPs by access count
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# Top 10 most-requested URLs
cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
> 💡 ヒント:ログ分析のパイプラインパターンは固定されています:`awk フィールド抽出 → sort → uniq -c 重複削除+カウント → sort -rn 降順 → head 上位N件`。このテンプレートを覚えれば、あらゆるログのTop N分析に適用できます。
# Requests per hour
cat access.log | awk -F: '{print $2":00"}' | sort | uniq -c | sort -rn
出力:
TEXT210 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分でレポート完成
小明は分析結果をレポートにまとめました — アクセストレンド、遅いリクエストトップ10、404エラーが最も多いページ。ボスは言いました:「よし、サーバーを増やそう。」
3. 知識ポイント
(1) 標準Nginxログフォーマット(combined)
192.168.1.100 - - [07/Jul/2026:10:15:30 +0000] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
│ │ │ │ │ │ │ │
│ │ │ └─ User-Agent │ │ │ └─ Response bytes
│ │ └───────────────────────────── Referer │ └────── Status code
│ └─────────────────────────────────────────────────────────── Request line (method/URL/protocol)
└─── Client IP Timestamp
ℹ️ 注意:Nginxのcombinedログフォーマットでは、フィールドはスペース区切りですが、一部のフィールド自体にスペースが含まれています(リクエスト行とタイムスタンプはブラケットと引用符で囲まれています)。awkがスペースで分割する場合、
$1はIP、$7はURL、$9はステータスコード、$10はバイト数 — これらのフィールド位置は安定しており、awkで直接抽出できます。
フィールド位置(スペース区切り):
| フィールド | 意味 |
|---|---|
$1 |
クライアントIP |
$4 |
タイムスタンプ [ 部分 |
$7 |
リクエストURL |
$9 |
HTTPステータスコード |
$10 |
レスポンスバイト数 |
(2) ▶ サンプル:サンプルログの生成
まず、分析用のサンプルログファイルを生成します:
#!/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 "Generated 1000 sample log entries to /tmp/access.log"
chmod +x generate-sample-log.sh
./generate-sample-log.sh
出力:
TEXTGenerated 1000 sample log entries to /tmp/access.log
(3) ▶ サンプル:リクエスト量統計
echo "=== Total Requests ==="
wc -l /tmp/access.log
echo ""
echo "=== Requests per IP ==="
cat /tmp/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Request Method Distribution ==="
cat /tmp/access.log | awk '{print $6}' | tr -d '"' | sort | uniq -c | sort -rn
出力:
TEXT=== Total Requests === 1000 /tmp/access.log === Requests per 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 === Request Method Distribution === 1000 GET
(4) ▶ サンプル:ステータスコード分析
echo "=== Status Code Distribution ==="
cat /tmp/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
echo ""
echo "=== 4xx Errors (Client Errors) ==="
cat /tmp/access.log | awk '$9 ~ /^4/ {print $9, $7, $1}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== 5xx Errors (Server Errors) ==="
cat /tmp/access.log | awk '$9 ~ /^5/ {print $9, $7, $1}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Error Rate ==="
TOTAL=$(wc -l < /tmp/access.log)
ERRORS=$(awk '$9 ~ /^[45]/ {count++} END {print count}' /tmp/access.log)
echo "Total requests: $TOTAL"
echo "Errors: $ERRORS"
echo "Error rate: $(echo "scale=2; $ERRORS * 100 / $TOTAL" | bc)%"
出力:
TEXT=== Status Code Distribution === 960 200 5 201 3 301 2 302 8 400 2 401 3 403 10 404 4 500 2 502 1 503 === 4xx Errors (Client Errors) === 3 404 /api/login 192.168.1.100 2 404 /about 10.0.0.45 2 403 /api/admin 192.168.1.200 === 5xx Errors (Server Errors) === 2 500 /api/data 10.0.0.88 2 502 /api/users 172.16.0.50 1 503 /api/health 192.168.1.100 === Error Rate === Total requests: 1000 Errors: 32 Error rate: 3.20%
(5) ▶ サンプル:URLホットスポット分析
echo "=== Most Popular URLs ==="
cat /tmp/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== URLs with Most 404s ==="
cat /tmp/access.log | awk '$9 == 404 {print $7}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== URLs with Largest Response Size ==="
cat /tmp/access.log | awk '{print $10, $7}' | sort -rn | head -10
出力:
TEXT=== Most Popular URLs === 150 /index.html 120 /api/users 110 /api/login 100 /about 90 /contact
=== URLs with Most 404s === 5 /api/login 3 /old-page 2 /favicon.ico
=== URLs with Largest Response Size === 10098 /images/logo.png 9876 /api/users 8765 /api/orders 7654 /products 6543 /blog
(6) ▶ サンプル:時間次元分析
echo "=== Requests per Hour ==="
cat /tmp/access.log | awk -F: '{print $2":00"}' | sort | uniq -c | sort -rn
echo ""
echo "=== Peak Traffic Hours ==="
cat /tmp/access.log | awk -F: '{print $2}' | sort | uniq -c | sort -rn | head -5
echo ""
echo "=== Requests per Minute (Top 10) ==="
cat /tmp/access.log | awk -F: '{print $2":"$3}' | sort | uniq -c | sort -rn | head -10
出力:
TEXT=== Requests per Hour === 85 10:00 80 14:00 75 09:00 70 22:00 65 18:00 === Peak Traffic Hours === 85 10 80 14 75 09 70 22 65 18 === Requests per Minute (Top 10) === 5 10:15 4 14:30 4 09:45 3 22:10 3 18:05
(7) ▶ サンプル:パフォーマンス分析
echo "=== Response Size Analysis (using $10 as simulated response size) ==="
echo "Largest response: $(awk '{print $10}' /tmp/access.log | sort -rn | head -1) bytes"
echo "Average response: $(awk '{sum += $10; count++} END {printf "%.0f", sum/count}' /tmp/access.log) bytes"
echo ""
echo "=== Large Responses (> 5KB) ==="
cat /tmp/access.log | awk '$10 > 5000 {print $10, $7}' | sort -rn | head -10
出力:
TEXT=== Response Size Analysis (using $10 as simulated response size) === Largest response: 10098 bytes Average response: 5142 bytes === Large Responses (> 5KB) === 10098 /images/logo.png 9876 /api/users 8765 /api/orders 7654 /products 6543 /blog 5678 /api/data
(8) ▶ 総合例:完全なログ分析レポートスクリプト
#!/bin/bash
# log-report.sh - Generate Nginx log analysis report
LOG_FILE="${1:-/tmp/access.log}"
if [ ! -f "$LOG_FILE" ]; then
echo "Error: Log file $LOG_FILE does not exist"
exit 1
fi
TOTAL=$(wc -l < "$LOG_FILE")
echo "========================================="
echo " Nginx Access Log Analysis Report"
echo " File: $LOG_FILE"
echo " Total requests: $TOTAL"
echo " Generated at: $(date)"
echo "========================================="
echo ""
# 1. Status code overview
echo "1. HTTP Status Code Distribution"
echo "--------------------"
cat "$LOG_FILE" | awk '{print $9}' | sort | uniq -c | sort -rn | \
awk '{printf " %-5s %s\n", $2, $1}'
echo ""
# 2. TOP 10 IPs
echo "2. Top 10 IPs by Access Count"
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 URLs
echo "3. Top 10 URLs by Request Count"
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 errors
echo "4. Error Requests"
echo "-------------"
FOURXX=$(awk '$9 ~ /^4/ {count++} END {print count}' "$LOG_FILE")
FIVEXX=$(awk '$9 ~ /^5/ {count++} END {print count}' "$LOG_FILE")
echo " 4xx errors: $FOURXX ($(echo "scale=1; $FOURXX*100/$TOTAL" | bc)%)"
echo " 5xx errors: $FIVEXX ($(echo "scale=1; $FIVEXX*100/$TOTAL" | bc)%)"
echo ""
# 5. Requests per hour
echo "5. Requests per Hour"
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. Largest responses
echo "6. Largest Responses (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 "End of Report"
# Run the report script
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で新しい行をリアルタイム追跡。grepと組み合わせ:tail -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]}'。
📖 まとめ
- Nginx標準ログフォーマット:IP/時刻/URL/ステータスコード/サイズ
awk '{print $1}'でIP抽出、$7でURL抽出、$9でステータスコード抽出sort | uniq -c | sort -rn | headでTop Nを発見awk -F: '{print $2":00"}'で時間別集計- 完全なログ分析スクリプトはbash 30〜50行で書ける
📝 練習問題
- 基本(難易度 ⭐):サンプルログ生成スクリプトを実行して1000件のテストデータを作成し、パイプラインチェーンでアクセス数トップ5のIPとそのリクエスト数を見つける
- 中級(難易度 ⭐⭐):404と500ステータスコードの数と割合をカウントし、
awk '{print $7}'でURLを抽出して最も人気のあるページトップ5を見つける - 上級(難易度 ⭐⭐⭐):フォーマット済みMarkdownレポートを出力するログ分析スクリプトを作成 — 総リクエスト数、Top 10 IP、ステータスコード分布、時間別リクエストトレンド、Top 5アクセスURLを含む