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

はじめに

CakePHP 3 には標準でエラーログを保存する機能があり logs/debug.log や logs/error.log に保存されます。 これを監視することで不具合への早期対応も可能になります。

エラーログには 404 Not Found エラーも含まれます。Webサイトを公開すると phpmyadmin 等を狙った推測 URL へのアクセスが増えたりします。error.log にはそれも記載されるため、肥大化して必要な情報が見つけにくくなる場合があります。

そこで今日は CakePHP 3.8 で 404 Not Found エラーのログを別ファイルに保存する方法をご紹介します。

CakePHP 3.2 以前では Middleware が実装されていないため動きません。また CakePHP 3.5 以前では、app.php の Log の className プロパティの書き方が異なるなど、他バージョンでは実装方法が異なる場合があるのでご注意ください。
目次
  1. app.php にログ設定を追加
  2. 独自の ErrorHandlerMiddleware を作成
  3. Application.php で独自の Middleware 読み込み
  4. ログ記載内容の変更
  5. おわりに

1. app.php にログ設定を追加

app.php の Log に 404 エラー用のログ設定を追加します。この例だと、ログは log/error_404.log に保存されます。

コード内の error404 の箇所は、アンダースコアを入れると正常に動作しませんでした。ハイフンは OK です。

/config/app.php
'Log' => [
  'error404' => [
    'className' => FileLog::class,
    'path' => LOGS,
    'file' => 'error_404',
    'url' => env('LOG_ERROR_URL', null),
    'scopes' => ['error404'],
  ],

  ...
],

2. 独自の ErrorHandlerMiddleware を作成

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

このコードは、404 扱いにしたい Exception をキャッチした際に、前述の app.php に追記した error404 の設定を使って、ログを保存する処理を行っています。

/src/Middleware/AppErrorHandlerMiddleware.php
<?php
namespace App\Middleware;

use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Log\Log;

class AppErrorHandlerMiddleware extends ErrorHandlerMiddleware
{
  protected function logException($request, $exception)
  {
    // 404 扱いとする Exception
    $targetExceptions = [
      'Cake\Routing\Exception\MissingControllerException',
      'Cake\Http\Exception\NotFoundException',
    ];
    // 404 の場合は別ファイルにログを記述
    if (in_array(get_class($exception), $targetExceptions)) {
      $message = $this->getMessage($request, $exception);
      Log::error($message, 'error404');

      return;
    }

    parent::logException($request, $exception);
  }
}

フォルダ構成は CakePHP 本体に合わせると src/Error/Middleware/ ですが、自作した Middleware は src/Middleware/ に置くべきみたいです。

3. Application.php で独自の Middleware 読み込み

Application.php で ErrorHandlerMiddleware の代わりに、作成した AppErrorHandlerMiddleware を使用するようにします。

<?php
namespace App;

use App\Middleware\AppErrorHandlerMiddleware; // ← 追記
...
// use Cake\Error\Middleware\ErrorHandlerMiddleware; // ← コメントアウト or 削除
...

  public function middleware($middlewareQueue)
  {
    $middlewareQueue
      // ->add(new ErrorHandlerMiddleware(null, Configure::read('Error'))) // ←コメントアウト or 削除
      ->add(new AppErrorHandlerMiddleware(null, Configure::read('Error'))) // ←追記
    ...
  }
...

4. ログ記載内容の変更

404 エラーにはトレース結果を残さなくてもよい、というような場合には、下記のように AppErrorHandlerMiddleware.php$message を任意のものに変更すれば OK です。

/src/Middleware/AppErrorHandlerMiddleware.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}";
  Log::error($message, 'error404');

  return;
}

5. おわりに

404 エラーを別ファイルに残すと error.log がすっきりします。またログ記載内容を必要なものだけにすれば、さらに保守性がよくなります。ログを使いやすく残すことは大切なので、必要に応じて CakePHP のログ機能をカスタムすると良いと思います。