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
処理の流れは次のとおりです。
grepで、PHP のエラーの見出し行だけを取り出します。スタックトレースの続きの行は数えません。sedで、先頭の時刻、行番号、引用符の中身、残りの数値を置き換えます。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を公開ディレクトリに置いたままにしない、ログの保存期間と権限を決める、といった扱いも合わせて整えます。