CakePHP 4.0 で 404 Not Found エラーを別ログに保存する方法

はじめに

CakePHP 4 にはエラーログを保存する機能があり、不具合の早期発見に役立ちます。エラーログは logs/error.log に保存されますが、この中には 404 Not Found エラーも含まれます。

Web サイトを公開すると wordpress や phpmyadmin などを狙った URL へのアクセスもあり、これらが error.log に貯まってしまうと必要な情報が見つけにくくなります。

そこで今日は CakePHP 4.0 で 404 Not Found エラーのログを別ファイルに保存する方法を、ログ内容の変更方法と併せてご紹介します。

昨年末に CakePHP 3.8 での方法をご紹介しましたが、CakePHP 4 はエラー周りのコードに変更が加えられており、手順が異なるのでご注意ください。

目次
  1. app.php に設定追加
  2. 独自の ErrorLogger を作成
  3. ログ記載内容の変更
  4. おわりに

1. app.php に設定追加

CakePHP 4.0 では、エラーログへの書込処理は /vendor/cakephp/cakephp/src/Error/ErrorLogger.php の log() 関数で行っています。

今回はこの ErrorLogger クラスを継承した、独自の AppErrorLogger クラスを作成して log() 関数をつくり、そこに 404 Not Found の場合は別ファイルにログを残す処理を書きます。

ErrorLogger の指定は config/app.php で下記のように行います。また 404 Not Found 用ログファイル(logs/error_404.log)の設定も追記します。

/config/app.php
<?php
use App\Error\AppErrorLogger; // ←追加
...
return [
    ...
    'Error' => [
        ...
        'errorLogger' => AppErrorLogger::class, // ← 追加
    ],
    ...
    'Log' => [
        // 以下を追加
        'error404' => [
            'className' => FileLog::class,
            'path' => LOGS,
            'file' => 'error_404',
            'url' => env('LOG_ERROR_URL', null),
            'scopes' => ['error404'],
        ],
        ...

errorLogger オプションは初期状態の app.php にはなく、公式ドキュメントで見つけました。

2. 独自の ErrorLogger を作成

下記内容で /src/Error/AppErrorLogger.php を作成します。

MissingControllerException が CakePHP 3 と異なっているので注意です。

/src/Error/AppErrorLogger.php
<?php
declare(strict_types=1);

namespace App\Error;

use Cake\Error\ErrorLogger;
use Cake\Log\Log;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

class AppErrorLogger extends ErrorLogger
{
    public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool
    {
        // 404 扱いとする Exception
        $targetExceptions = [
            'Cake\Controller\Exception\MissingActionException',
            'Cake\Http\Exception\MissingControllerException',
            'Cake\Http\Exception\NotFoundException',
        ];
        // 404 の場合は別ファイルにログを記述
        if (in_array(get_class($exception), $targetExceptions)) {
            $message = $this->getMessage($exception);
            if ($request !== null) {
                $message .= $this->getRequestContext($request);
            }
            $message .= "\n\n";

            return Log::error($message, ['scope' => 'error404']);
        }

        return parent::log($exception, $request);
    }
}

以上で完了です。あとは存在しないアクションにアクセスしたり、NotFoundException を発生させて動作を確認してください。

3. ログ記載内容の変更

ログ内容を変更したい場合は、log() 関数の $message の箇所を下記のように変更してください。

/src/Error/AppErrorLogger.php
// 404 の場合は別ファイルにログを記述
if (in_array(get_class($exception), $targetExceptions)) {
    // 独自のログ内容
    $url = $_SERVER['REQUEST_URI'];
    $ip = $_SERVER['REMOTE_ADDR'];
    $ua = $_SERVER['HTTP_USER_AGENT'];
    $message = "{$url}\t{$ip}\t{$ua}";

    return Log::error($message, ['scope' => 'error404']);
}

4. おわりに

CakePHP 4.0 は 3.0 に似ていて、比較的多くのコードを流用できていますが、エラーログ周りのロジックには変更があり、今回ご紹介したログ分割は実装方法が大きく異なりました。

まだ CakePHP 4.0 の資料が少ないため、今回は本体のコードを分析して実装しました。独自の ErrorMiddleware と ErrorHandler の組み合わせも考えたのですが、今のところは上記の AppErrorLogger で行うのが一番簡潔かなと考えています。