Home > ブログ > Laravelでスクリプトを停止させずE_NOTICEのログを残す

ブログ

Laravelでスクリプトを停止させずE_NOTICEのログを残す

LaravelでE_NOTICEの発生でスクリプトは止めたくないが、ログには残しておきたいという場合の対処法。

PHPではerror_reporting設定で出力するエラーの種類を変更することができます。 Laravelではデフォルトでerror_reporting(-1)となっており全エラーを報告するようになっています。Laravelではエラーが報告されると(E_NOTICEも含め)全て例外となり、スクリプトの実行が停止してエラーページが表示されます。

このため、PHPの本来の動作としてはE_NOTICE, E_WARNINGのエラーについてはスクリプトの実行は中断されませんが、LaravelにおいてはE_NOTICE, E_WARNINGでもスクリプトの実行が停止してしまいます(図1)。

Laravelのエラーページ
図1 E_NOTICEで停止

E_NOTICEについては、配列に未定義のindexでアクセス($foo['bar'])した場合など、軽度のもので発生するので、これでスクリプトが停止してしまうのはキツいということで、AppServiceProviderなどでerror_reporting(E_ALL & ~E_NOTICE)のように設定しておくこともあると思います。

ただし、これだとE_NOTICE自体が報告されなくなるので、E_NOTICEが発生しているかがわからなくなり、E_NOTICEを修正していくのが難しくなります。開発環境と本番環境で設定を分けて、開発環境ではerror_reporting(E_ALL)で動作させておけばいいのですのが、ここでは、E_NOTICEが発生してもスクリプトの実行は止めず、ログには残してE_NOTICEが発生したことを確認できるようにしてみます。

実装例は以下のようになります。

実装例

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // E_NOTICEはLaravelに渡さないように設定
        $this->takeAwayErrors([E_NOTICE]);
        :
        略
    }

    :
    略

    private function takeAwayErrors($stealLevels)
    {
        $laravelHandler = set_error_handler(function () {});
        restore_error_handler();

        $handler = function ($level, $message, $file = '', $line = 0)
          use ($stealLevels, $laravelHandler) {
            if (!is_callable($laravelHandler)) {
                // phpのエラーハンドラに渡す
                return false;
            }

            // $stealLevelsに指定されたエラーレベルならphpのエラーハンドラに渡す
            if (array_search($level, $stealLevels) !== false) {
                return false;
            }

            // laravelのエラーハンドラへ
            // error_reporting()に該当するエラーは全て例外となる
            return call_user_func(
                $laravelHandler,
                $level,
                $message,
                $file,
                $line
            );
        };
        set_error_handler($handler);
    }

この例ではAppServiceProviderに実装しています。

takeAwayErrors()で何をやっているかというと、ユーザー定義のエラーハンドラを作成し、指定レベル($stealLevels)のエラーについてはLaravelのエラーハンドラに渡さずにPHPのデフォルトのエラーハンドラに渡すようにしています。それ以外のエラーについては、元々登録されていたLaravelのエラーハンドラに渡し例外となります。

Laravelから横取りするエラーの種類は以下のように配列で指定します。

    $this->takeAwayErrors([E_NOTICE]);

これで、E_NOTICEについてはPHPのデフォルトのエラーハンドラに渡されるようになるので、ログのみ取られてスクリプトの実行は中断されなくなります(*1)。

なお、ログはLaravelのstorage/logsディレクトリではなくphpのerror_log設定で指定したファイルに出力されます。

E_WARNINGでも停止させたくない場合は以下のように指定します。

    $this->takeAwayErrors([E_NOTICE, E_WARNING]);

PHP8では多くのE_NOTICEエラーがE_WARNINGに変更されたので、このような設定にする必要が出てくるかもしれません。

なぜこんなことをしたかというと、以前引き継いだシステムがE_NOTICEを無視していたので、動作中のシステムを動かしながらE_NOTICEの発生状況を確認したかったためです。これでシステムを動かしながらE_NOTICEの発生を監視&修正していくことができます。 error_reporting(E_ALL & ~E_NOTICE)で無条件に無視し続けるよりはいいのではないでしょうか。

ちなみに、Laravelの標準のエラーハンドラはIlluminate/Foundation/Bootstrap/HandleExceptionsクラスのhandleError()メソッドにあります。error_reporting()にマッチするエラーは全て例外を投げる動作になっています。

Laravelの標準のエラーハンドラ

class HandleExceptions
{
    :
    public function handleError($level, $message, $file = '', $line = 0, $context = [])
    {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }

(*1) error_reporting(E_ALL)のようにE_NOTICEを含めた設定になっている必要があるのに注意。error_reporting(E_ALL & ~E_NOTICE)になっているとPHPのデフォルトのハンドラでも無視されるのでログには残りません。

投稿日:2021/01/22 00:50

タグ: PHP laravel

Top

アーカイブ

タグ

Server (28) 作業実績 (21) PHP (19) ネットワーク (17) プログラミング (15) OpenSSL (10) C (8) C++ (8) PHP関連更新作業 (8) EC-CUBE (7) Webアプリ (7) laravel (6) 書籍 (5) Nginx (5) Linux (5) AWS (4) Vue.js (4) JavaScript (4) 与太話 (4) Rust (3) Symfony (2) お知らせ (2) Golang (2) OSS (1) MySQL (1) デモ (1) CreateJS (1) Apache (1)