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 (10) 作業実績 (10) C++ (6) Webアプリ (5) PHP (5) laravel (4) Linux (4) プログラミング (3) ネットワーク (3) JavaScript (3) 書籍 (2) Nginx (2) Vue.js (2) Golang (2) EC-CUBE (2) C (1) デモ (1) CreateJS (1) AWS (1)

技術的な情報は以下にもあります。