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;

/**
  * Log errors and unhandled exceptions to `Cake\Log\Log`
  */
class AppErrorLogger extends ErrorLogger
{
  /**
    * Generate the error log message.
    *
    * @param \Throwable $exception The exception to log a message for.
    * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available.
    * @return bool
    */
  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 で行うのが一番簡潔かなと考えています。