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)。
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