2026年7月30日

2026年7月30日

ログ分析でエラーパターンを検出する方法(変わる部分を置き換えて集計し、新しいエラーと急増を見つける)

ログからエラーパターンを見つけるには、1行ずつ読むのではなく、時刻・数値・IP アドレスなど毎回変わる部分を置き換えて「同じ種類のエラー」をまとめ、件数で並べるのが近道です。こうすると、数千行のログでも「どのエラーが何件出ているか」「昨日までなかったエラーはどれか」「何時ごろから急に増えたか」が数行に縮まります。この記事では、WordPress のサーバーで扱うことの多い PHP のエラーログ、Nginx のアクセスログとエラーログ、systemd のジャーナルを例に、コマンドでの集計から、新しいパターンを自動で知らせる仕組みまでを説明します。

「パターン」としてまとめる考え方

同じ原因のエラーでも、ログの行は毎回少しずつ違います。

  • 時刻が違う
  • エラーになった値(配列のキー名、メモリの量など)が違う
  • アクセス元の IP アドレスやリクエストの URL が違う

このままでは sort | uniq -c で数えても全行が別物として扱われます。そこで、変わる部分を N や STR のような決まった文字に置き換え(正規化し)てから数えます。置き換え後に同じ文字列になった行が、同じパターンです。

置き換えすぎると別のエラーが1つにまとまってしまい、置き換えが足りないと同じエラーがばらばらになります。最初は数値と引用符の中身を置き換える程度から始め、結果を見ながら調整するのが実用的です。

PHPのエラーログを集計する

WordPress で WP_DEBUG_LOG を有効にしている場合、既定では wp-content/debug.log にエラーが記録されます。サーバーの PHP の設定によっては、PHP-FPM のログや error_log で指定した場所に出ていることもあります。

LOG=/var/www/html/wp-content/debug.log

grep -E '^\[[^]]*\] PHP (Fatal error|Parse error|Warning|Notice|Deprecated)' "$LOG" \
  | sed -E \
      -e 's/^\[[^]]*\] //' \
      -e 's/ on line [0-9]+/ on line N/' \
      -e 's/"[^"]*"/"STR"/g' \
      -e "s/'[^']*'/'STR'/g" \
      -e 's/[0-9]+/N/g' \
  | sort | uniq -c | sort -rn | head -20

処理の流れは次のとおりです。

  1. grep で、PHP のエラーの見出し行だけを取り出します。スタックトレースの続きの行は数えません。
  2. sed で、先頭の時刻、行番号、引用符の中身、残りの数値を置き換えます。
  3. sort | uniq -c | sort -rn で、同じパターンの件数を数えて多い順に並べます。

結果は次のような形になります(例)。

   2841 PHP Warning:  Undefined array key "STR" in /var/www/html/wp-content/plugins/example/example.php on line N
     37 PHP Deprecated:  Creation of dynamic property ...
      3 PHP Fatal error:  Allowed memory size of N bytes exhausted ...

件数の多さと深刻さは別物である点に注意してください。上の例では Warning が最も多いものの、サイトを止めるのは件数の少ない Fatal error のほうです。読む順番は、Fatal error と Parse error、Warning、Notice と Deprecated の順にすると、影響の大きいものから手を付けられます。ファイルのパスにプラグインやテーマのフォルダ名が出るので、どの部品が原因かもここで絞り込めます。

なお wp-content/debug.log は、そのままだと Web から直接開けてしまう場所にあります。エラーの内容にはファイルのパスなど内部の情報が含まれるため、WP_DEBUG_LOG にファイルのパスを指定して公開ディレクトリの外に出すか、Web サーバーの設定でアクセスを拒否してください。調査が終わったら WP_DEBUG を無効に戻すことも大切です。

Nginxのログで5xxエラーの急増を見つける

サイトの利用者に影響が出ているかは、アクセスログのステータスコードで判断できます。標準の combined 形式なら、9番目の項目がステータスコードです。

# 5xxエラーの件数を1分ごとに数える
awk '$9 ~ /^5/ {print substr($4, 2, 17)}' /var/log/nginx/access.log | uniq -c | tail -30

# 5xxを返したURLを、クエリ文字列を除いて多い順に並べる
awk '$9 ~ /^5/ {sub(/\?.*/, "", $7); print $9, $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -20

1つ目は時刻の「日/月/年:時:分」までを取り出しているので、分単位の件数になります。普段はほとんど 0 なのに、ある時刻から件数が並び始めていれば、そこが障害の始まりです。2つ目で、エラーが特定の URL(たとえば admin-ajax.php や特定のプラグインの API)に集中しているかを確認します。

ログの形式を独自に変えている場合や、壊れたリクエストが混ざっている場合は、項目の位置がずれます。結果がおかしいときは、まず head で実際の行を見て、何番目の項目がステータスコードかを確かめてください。

エラーの理由は Nginx のエラーログにあります。こちらも接続番号や IP アドレス、リクエストの内容を置き換えてから数えます。

sed -E \
  -e 's|^[0-9/]+ [0-9:]+ ||' \
  -e 's/[0-9]+#[0-9]+: \*[0-9]+ //' \
  -e 's/client: [^,]+/client: IP/' \
  -e 's/request: "[^"]*"/request: REQ/' \
  /var/log/nginx/error.log | sort | uniq -c | sort -rn | head -20

よく見るパターンと、疑う場所は次のとおりです。

  • upstream timed out: PHP の処理が Nginx の待ち時間内に終わっていません。PHP のエラーログやスロークエリのログで、重い処理を探します。
  • connect() ... failed や No such file or directory(PHP-FPM のソケット): PHP-FPM が止まっているか、ソケットのパスが設定と合っていません。
  • upstream sent too big header: PHP から返るヘッダーが大きすぎます。Cookie やリダイレクトの多いプラグインが関係していることがあります。

systemdのジャーナルからエラーを拾う

OS やサービス(MySQL、PHP-FPM など)のエラーは、ジャーナルに優先度付きで記録されています。-p err で優先度が err 以上のものだけを取り出し、同じように正規化して数えます。

journalctl -p err --since "24 hours ago" -o cat \
  | sed -E 's/[0-9]+/N/g' | sort | uniq -c | sort -rn | head -20

-o cat は時刻やホスト名を省いてメッセージだけを出す指定で、集計の前処理が少なくて済みます。どのサービスのエラーかを残したい場合は、-u php8.3-fpm のようにユニットを指定して個別に実行します(ユニット名は環境に合わせて確認してください)。

新しいパターンを自動で知らせる

毎回コマンドを打つ代わりに、「これまでに見たことのないパターンが現れたら知らせる」仕組みにすると、見落としが減ります。既知のパターンを1つのファイルにためておき、今回の結果と比べて新しいものだけを取り出します。

#!/bin/bash
# /usr/local/bin/php-error-patterns.sh
set -u
export LC_ALL=C   # sort と comm の並び順をそろえる

LOG=/var/www/html/wp-content/debug.log
DIR=/var/lib/log-patterns
KNOWN="$DIR/known.txt"
CURRENT="$DIR/current.txt"

mkdir -p "$DIR"
touch "$KNOWN"

grep -E '^\[[^]]*\] PHP (Fatal error|Parse error|Warning|Notice|Deprecated)' "$LOG" \
  | sed -E -e 's/^\[[^]]*\] //' -e 's/ on line [0-9]+/ on line N/' \
           -e 's/"[^"]*"/"STR"/g' -e 's/[0-9]+/N/g' \
  | sort -u > "$CURRENT"

new=$(comm -13 "$KNOWN" "$CURRENT")
if [ -n "$new" ]; then
  printf '%s\n' "$new" | logger -t log-patterns
  # 通知が必要なら、ここでメールやチャットのWebhookに送る
fi

sort -u "$KNOWN" "$CURRENT" -o "$KNOWN"
# /etc/cron.d/php-error-patterns(1時間ごと)
0 * * * * root /usr/local/bin/php-error-patterns.sh

comm -13 は「2つ目のファイルにだけある行」を出すので、既知のファイルにない新しいパターンだけが残ります。最初の1回はすべてが新しいパターンとして出るため、初回の結果は確認だけして既知として登録し、2回目以降の差分を見る運用にします。プラグインの更新後に新しい Warning が出始めた、といった変化をすぐに拾えます。

件数の急増を知らせたい場合は、1時間ごとの 5xx の件数を記録し、普段の件数より大きく多いときに通知する、という形に広げられます。しきい値は固定値より「過去数日の同じ時間帯の何倍か」で決めると、アクセスの多い時間帯に誤って鳴りにくくなります。

ツールを使う場合

コマンドでの集計に慣れたら、目的に応じて次のようなツールを使うと手間が減ります(機能や対応形式は版によって変わります。2026年10月現在の一般的な使い方として紹介します)。

  • lnav: ターミナルでログを見るためのツールで、形式を自動で判別し、色分けや絞り込み、SQL 風の集計ができます。1台のサーバーでの調査に向いています。
  • GoAccess: Nginx や Apache のアクセスログを集計し、ステータスコードや URL ごとの件数をレポートにします。
  • Grafana Loki: 複数のサーバーのログを集め、LogQL という問い合わせ言語で件数の推移をグラフにしたり、アラートを設定したりできます。たとえば sum(count_over_time({job="nginx"} |= "upstream timed out" [5m])) のように、特定の文字列を含む行の件数を5分ごとに数えられます。

ログを扱うときの注意

アクセスログには IP アドレスや URL のクエリ文字列が含まれ、内容によっては個人に関わる情報になります。保存期間を決めて logrotate で古いものを消す、集計結果を共有するときは IP アドレスを伏せる、ログファイルの権限を必要な人だけに絞る、といった扱いを決めておいてください。サイトのプライバシーポリシーで記載しているログの扱いと、実際の運用が食い違わないようにすることも大切です。

まとめ

  • エラーパターンの検出は、時刻・数値・引用符の中身などを置き換えて同じ種類の行をまとめ、件数で並べるのが基本です。
  • PHP のエラーログは Fatal error から順に読み、ファイルのパスで原因のプラグインやテーマを絞り込みます。
  • Nginx はアクセスログで 5xx の急増と集中している URL を見つけ、エラーログで理由を確認します。
  • 既知のパターンと比べる仕組みを cron で動かすと、更新後に現れた新しいエラーを自動で拾えます。
  • debug.log を公開ディレクトリに置いたままにしない、ログの保存期間と権限を決める、といった扱いも合わせて整えます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲