0Pricing
Linux Command Line & Bash Scripting Mastery · レッスン

大規模なWeb・アプリケーションログの解析

grep、cut、awkを使い、アクセスログからステータスコード、レイテンシ、クライアント情報を抽出します。

「大規模なWeb・アプリケーションログの解析」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

Web アクセスログとは

Apache、Nginx、Caddy などのすべての HTTP サーバーは、リクエストごとにアクセスログへ1行を書き込みます。この行の構造を理解することが、ログ分析作業の基礎となります。

一般的な Combined Log Format (CLF) の行は次のようになります。

  • クライアント IP — リクエストを行った相手
  • タイムスタンプ — リクエストが発生した時刻
  • リクエスト行 — メソッド、パス、プロトコル
  • ステータスコード — HTTP レスポンス (200、404、500…)
  • 送信バイト数 — レスポンス本文のサイズ
  • Referer — リクエスト元のページ
  • User-Agent — ブラウザまたは bot を示す文字列

/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"

大規模な環境では、これらのファイルは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 ' のように空白を含めて照合すれば、文字列 500 を含む URL パスを誤って一致させるのを防げます。

#!/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 は各行を区切り文字で分割し、指定したフィールドを表示します。Combined Log Format では、空白で分割するとステータスコードは9番目のフィールドになります。ただし、リクエスト行が引用符で囲まれているため、既知の位置を基準に数えるほうが安全です。

信頼性の高い方法は、リクエスト行が必ず引用符で囲まれることを利用するものです。ステータスコードは、リクエストフィールドを閉じる引用符の後に必ず現れる最初のトークンです。cut -d'"' -f3 でリクエスト部分の引用符より後ろをすべて取り出し、続いて cut -d' ' -f2 でステータスコードを取り出します。

この2段階の 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 ブロックで並べ替えた集計結果を表示します。

cut | sort | uniq -c より awk を使う理由は何でしょうか。それは、awk なら1回の走査で処理でき、ファイル全体を先にソートする必要がないからです。ログが数百 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 の抽出

運用でよく行う作業の1つに、最も多くのエラーを発生させているクライアント 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

User-Agent の抽出と bot の検出

User-Agent フィールド (" で分割した場合の6番目のフィールド) はクライアントを識別します。クローラー、スクレイパー、悪意のある bot は、メトリクスを汚染したり、エラー数を過剰に増やしたりすることがあります。これらを除外すると、実際のユーザートラフィックをより正確に把握できます。

よくある bot の識別文字列: bot、crawler、spider、curl、python-requests、Googlebot、Bingbot。

grep -iv (大文字と小文字を区別しない反転検索) で既知の bot を除外するか、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 (リクエスト行) を取り出した後、メソッドとプロトコルを切り出してパスだけを抽出します。/users/12345 のようなパスパラメータを持つ API では、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 によるエンドポイント別エラーの相関分析

最も強力な1回の走査による分析では、エンドポイント、ステータスコード、必要に応じてレイテンシなど、複数のフィールドを同時に扱います。複合値をキーにした awk の連想配列を使うと、処理を簡潔かつ高速に実装できます。

以下のパターンは、1回の走査でエンドポイントごとの5xxエラーを集計します。一時ファイルは不要で、途中のソートも最後まで行いません。大規模なログから1分以内に答えを得る必要がある本番環境の可観測性スクリプトでは、この方法が使われます。

#!/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

ローテーション済みログと圧縮ログの処理

ほとんどのサーバーでは、ログが毎日ローテーションされます。古いファイルは access.log.1.gz、access.log.2.gz などの名前で gzip 圧縮されます。標準ツールではこれらを直接読み取れませんが、次の2つの方法なら簡単に処理できます。

  • zcat — 標準出力に展開し、パイプラインへ渡す
  • zgrep — gzip ファイルを展開せずに、その内部を直接 grep する

非圧縮ファイルと圧縮ファイルの両方にまたがる1週間分のログを分析するには、プロセス置換を使うか、zcat で連結します。以下のコードは、現在のライブログに加えて直近7個のローテーション済みファイルを、単一の awk 呼び出しで処理します。一時ファイルは必要ありません。

#!/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

Combined Log Format で HTTP ステータスコードを保持する awk フィールドはどれですか

awkのワンライナーを使って、標準的なNginxアクセスログのCombined Log Format(スペース区切りで、リクエスト行は引用符で囲まれています)からHTTP 4xxレスポンスだけをフィルタリングしています。HTTPステータスコードを正しく識別するフィールド番号はどれですか。

レッスンのまとめ:ログ分析パイプライン

このレッスンでは、標準的なBASHユーティリティだけを使って、Webログやアプリケーションログを大規模に分析するための完全なツールキットを構築しました。

扱った主なテクニック:

  • まず構造を把握する — Combined Log Formatには予測可能なフィールド構成があります。これを理解していれば、cut -d'"'やawkのフィールド参照を使って確実に分割できます。
  • ステータスコードの抽出 — awk '{ count[$9]++ }'を使うと、1回の走査ですべてのコードをカウントできます。サーバーエラーを対象にするには、$9 ~ /^5/でフィルタリングします。
  • レイテンシー分析 — awkでカスタムフィールドのrt=を解析すると、外部ツールなしで平均値、最大値、ヒストグラムの区間を計算できます。
  • クライアントとボットの分析 — -F'"'を使って"で分割し、User-Agentフィールドに到達します。集計前にgrep -ivをパイプでつないでボットを除外します。
  • エンドポイントの正規化 — awk内でgsub(/\/[0-9]+/, "/:id")を使い、カウント前にパラメーター付きパスをまとめます。
  • ローテーションされたログ — サブシェル内でcatとzcatを組み合わせ、ローテーションされたすべてのファイルを1回のパイプライン処理に渡します。

これらのパターンは組み合わせて使えます。フィルタリング、抽出、正規化、集計を1つのパイプラインにつなぎ、一般的なハードウェアで数億行を処理できます。これらの基本要素を身につければ、アドホックなインシデント調査で専用のログ集約サービスが必要になることはほとんどありません。

よくある質問

「大規模なWeb・アプリケーションログの解析」レッスンは無料ですか?

はい。「大規模なWeb・アプリケーションログの解析」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Linux Command Line & Bash Scripting Masteryコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

「大規模なWeb・アプリケーションログの解析」で何を学びますか?

grep、cut、awkを使い、アクセスログからステータスコード、レイテンシ、クライアント情報を抽出します。 ブラウザで直接実行するハンズオンコードでLinux Command Line & Bash Scripting Masteryを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Linux Command Line & Bash Scripting Masteryを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLinux Command Line & Bash Scripting Masteryは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「大規模なWeb・アプリケーションログの解析」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLinux Command Line & Bash Scripting Masteryレッスンでコードを書いて実行できますか?

はい。すべてのLinux Command Line & Bash Scripting Masteryレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 大規模なWeb・アプリケーションログの解析
  2. リアルタイムのログ追跡とストリーミングアラート
  3. スクリプトでjournalctlを使ったjournaldの検索
  4. ログストリームからのメトリクスとヒストグラムの計算
← Linux Command Line & Bash Scripting Masteryに戻る