tanaka's Programming Memo

プログラミングについてのメモ。

Laravel(5.3) ErrorとLoggingのチュートリアル

Errors & Logging - Laravel - The PHP Framework For Web Artisans

Laravelの公式ドキュメントを元に、エラーとログの動作を確認します。

Laravelのエラーとログ処理

Laravelには、あらかじめエラーや例外を処理するための設定がなされています。 App\Exceptions\Handler クラスには、作成するアプリから呼ばれるすべての例外があり、ログをファイルに保存したり、ブラウザに例外内容を表示したりします。

ログの記録のために、Laravelは Monolog ライブラリを利用しています。単一のログファイル、ローテーションするログファイル、システムログにエラー情報を書き込むなどのログの記録方法が用意されていて、設定で選択できます。

(Errors & Logging - Laravel - The PHP Framework For Web Artisans より)

前提

  • macOS Sierra
  • PHP5.6以降
  • MySQL 5.7.11
  • node、Laravel、Composerはインストール済み

プロジェクトを準備

ログやエラー表示を実際に動かして、色々と試してみます。以下の手順で、プロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-error-test
cd laravel-error-test
php artisan serve

以上で準備完了です。Webブラウザを起動して、 http://localhost:8000 にアクセスすると、インストールしたLaravelプロジェクトが表示されれば成功です。

エラー表示とログを確認する

エラーは、サーバーにアクセスした時のHTTPエラーコードによって例外が発生した時、あるいは、アプリ内から abort() メソッドを呼び出した時に発生します。この時、設定したレベルに応じてログファイルへの情報書き出しと、画面にエラー内容の表示の2種類の出力が行われます。

また、ログファイルには、 Log ファサードを使って、情報を書き込むことができます。

これらを発生させて、画面に表示されるものや、ログファイルを覗いてみましょう。

例外を発生させてみる

http://localhost:8000/test にアクセスしてみましょう。このルートは定義していないので、ページが見つからないというエラー(Sorry, the page you are looking for could not be found.)が画面に表示されます。

ログファイルはどうなっているでしょう。ログファイルは、 storage/logs フォルダー内に生成されるはずですが見当たりません。これは、HTTP関連のエラーは、ログに出力されない設定になっているからです。変更してみましょう。

  • app/Exceptions/Handler.php を開く
  • $dontReport 配列を探して、以下のように HttpException クラスの行をコメントアウトする
    protected $dontReport = [
        \Illuminate\Auth\AuthenticationException::class,
        \Illuminate\Auth\Access\AuthorizationException::class,
//        \Symfony\Component\HttpKernel\Exception\HttpException::class,
        \Illuminate\Database\Eloquent\ModelNotFoundException::class,
        \Illuminate\Session\TokenMismatchException::class,
        \Illuminate\Validation\ValidationException::class,
    ];
  • 上書き保存する

再度 http://localhost:8000/test にアクセスするか、リロードしてください。今度は storage/logs/laravel.log が生成されます。開いて内容を確認してみましょう。画面に表示されたのと同様のことが書き込まれています。

abort()メソッドで、自分で例外を発生させる

abort()ヘルパーメソッドを利用することで、自分で例外を発生させることができます。HTTPステータスコードを引数に渡します。試してみましょう。

  • routes/web.php を開く
  • getの処理を以下のようにabortを追加
Route::get('/', function () {
    abort(500);

    return view('welcome');
});
  • 上書き保存する

http://localhost:8000 を開くと、ステータスコード500の時のエラー(Whoops, looks like something went wrong.)が発生します。また storage/logs/laravel.log に、ログが追加されます。

abort()の第2引数で文字列を渡すと、その文字列をエラーのテキストに含めることができます。 routes/web.php のgetルートを以下に書き換えます。

Route::get('/', function () {
    abort(500, '例外テスト');

    return view('welcome');
});
  • 上書き保存

以上で、 http://localhost:8000 を再読み込みすると、エラーメッセージに「例外テスト」という文字列が追加されます。

Logを出力する

Logファサードを利用すれば、アプリからログファイルに情報を出力することができます。 routes/web.php を開いて、 getメソッドの処理を以下のように修正してください。

Route::get('/', function () {
    Log::info('ログ出力');

    return view('welcome');
});

上書き保存をしたら http://localhost:8000 を再読み込みします。エラーにはならず、Laravelのページが表示されます。 storage/logs/laravel.log を開いて最後の行を見ると、「ログ出力」というメッセージが追加されています。アプリの動作を妨げないで情報だけを残したい場合などにこの機能を使います。

Log::info()のinfoの部分が出力するレベルを決めています。使えるメソッドは以下の通りです。それぞれの意味については後述します。

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

エラーの画面表示と、ログへの出力を確認しました。まとめると以下の通りです。

  • 例外が発生したら、ブラウザにエラーが表示された上で、 Handler.php の $dontReport 配列にリストアップされていない例外は、ログファイルに情報が書き込まれる
  • abort()メソッドを使うと、自前でHTTPステータスコードを指定して例外を発生させることができる
  • Log::info()などのLogファサードを利用することで、自前でログファイルに情報を書き込むことができる

HTTPメッセージをログに残すと煩わしいので、 Handler.php の $dontReport でコメントアウトした行を元に戻しておきましょう。

エラーの設定

設定の場所

config/app.php ファイルに、debub の項目があり、ユーザーにどの程度の情報を表示するかを設定します。通常は .env ファイルの APP_DEBUG に定義すると良いでしょう。

ローカル環境では、 APP_DEBUG は true に設定しておきます。公開環境では、重要な設定がエンドユーザーに見られるのは危険なので false にします。デバッグをオフにしてみましょう。

  • .env を開く
  • 「APP_DEBUG=true;」の部分を、以下のように書き換える
APP_DEBUG=false
  • 上書き保存をする

http://localhost:8000/test にアクセスしてください。先ほどと同様にエラーが画面に表示されます。これは、 .env は、アプリの起動時に読み込まれるため、アプリを再起動する必要があるからです。ターミナルで[control]+[C]を押してサービスを停止したら、以下でサービスを再起動します。

php artisan serve

http://localhost:8000/test をリロードすると、今度はエラーの詳細が表示されなくなります。動作を確認したら、[APP_DEBUG]をtrueに戻して、サービスを再起動して元に戻しておいてください。

ログファイルの種類

ログファイルは、 single, daily, syslog, errorlog の4種類の保存方法が用意されています。 .env ファイルの APP_LOG に、利用したい保存方法の文字列を設定します。デフォルトは single 、つまり1つのログファイルに出力するようになっています。

daily にした場合、デフォルトでは5日分のデータが保存されます。これを変更したい場合は、 config/app.php に、 'log_max_files' => 30 のように追加します。

.env の APP_LOG_LEVEL で、ログに残すエラーのレベルを設定できます。先ほど、Log::info()でログを書き出しましたが、info()もエラーレベルの一つです。レベルには以下があり、デフォルトは debug になっています。

  • debug, info, notice, warning, error, critical, alert, emergency

設定したレベルを含む、上記の右側の属性の情報がログに記録されます。 debug では、すべてのメッセージが出力されます。error では、 error, critical, alert, emergency のみが記録されます。

エラーレベルによって、出力が制御されることを確認してみましょう。

  • .env を開く
  • 以下のようにして、 APP_LOG_LEVEL を error にする
APP_LOG_LEVEL=error
  • .env を上書き保存
  • ターミナルで[control]+[C]で、サービスを停止
  • php artisan serve でサービスを再起動する

http://localhost:8000 にアクセスしたら、 storage/logs/laravel.log を確認してください。「ログ出力」という文字は出力されていません。設定を元に戻して、同じ操作をしたら、「ログ出力」が追加されます。

連想配列をログに出力

ログに、連想配列に保存されているデータを渡して記録することができます。 routes/web.php を開いて、 get()メソッドの処理を以下のように修正します。

Route::get('/', function () {
    Log::error('ログ出力', ['id' => 1, 'name' => 'MyName']);

    return view('welcome');
});

上書き保存をしたら、 http://localhost:8000 を開き直します。画面が表示されたら、ログファイル( storage/logs/laravel.log )を開きます。最後に配列の内容が追加されたログが書き込まれます。

[20xx-xx-xx xx:xx:xx] local.ERROR: ログ出力 {"id":1,"name":"MyName"} 

独自のHTTPエラーページ

Laravelは、HTTPステータスコードに応じた独自のエラーページを簡単に作成することができます。例えば、404エラーに対するエラーであれば、 resources/views/errors/404.blade.php を生成して、そこに表示内容を記述します。このディレクトリー内のviewのファイル名は、HTTPエラーコードに対応します。HttpExceptionは、 abort ヘルパーから呼ばれて、 $exception の変数として viewに渡されます。

実際に404エラーのページを作成してみましょう。まずは、 http://localhost:8000/test にアクセスして、現状を確認します。「Sorry, the page you are looking for could not be found.」というタイトルが表示されて、その下にエラーの詳細が表示されています。これを、「ページが見つかりませんでした。」という日本語のメッセージに変更します。

  • resources/views/errors/503.blade.php を複製して、 404.blade.php というファイル名にする
  • 404.blade.php を開いて、以下の通り修正する
    • 3行目
        <title>Page Not Found.</title>
    • 43行目
                <div class="title">ページが見つかりませんでした。</div>
  • 上書き保存

以上出来たら、 http://localhost:8000/test を開き直すか、リロードします。画面いっぱいに「ページが見つかりませんでした。」に変わります。

404.blade.php を削除すると、元に戻ります。

その他

ここまでで、最低限のエラーとログの使い方は紹介しました。これ以降は、公式ドキュメントに掲載されていて、ここまでで紹介していない機能について記載します。

例外ハンドラー

report メソッド

すべての例外ハンドラーは App\Exceptions\Handler クラスに定義されています。このクラスは、 report と render メソッドを持ちます。

report メソッドは、例外をログに残したり、BugsnagやSentryのような外部サービスへの記録のコードを追加することができます。デフォルトでは、ハンドラーのベースクラスに例外をそのまま渡して、規定のルールに基づいてログファイルに情報を追加します。

独自に作成した例外など、個別の例外処理を行いたい場合は、PHPのinstanceof を利用して、以下のようにします。

/**
 * Report or log an exception.
 *
 * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
 *
 * @param  \Exception  $exception
 * @return void
 */
public function report(Exception $exception)
{
    if ($exception instanceof CustomException) {
        // ここに、独自の例外用の処理を書く
    }

    return parent::report($exception);
}
render メソッド

renderメソッドは、Webブラウザーに描画できるように例外の内容を HTTP response に変換して返します。デフォルトでは、例外はレスポンスを生成したベースクラスにそのまま渡しているだけです。

reportと同様に、独自の例外を PHP の instanceof でクラスタイプを判別して、独自のエラー画面を返すことができます。

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

    return parent::render($request, $exception);
}

Monologのカスタム設定

Monologの設定を完全にコントロールしたい場合は、アプリの configureMonologUsing メソッドを利用します。 bootstrap/app.php ファイルの、 $app をreturnする直前に、このメソッドを呼びます。

$app->configureMonologUsing(function($monolog) {
  $monolog->pushHandler(...);
});

return $app;

Monologインスタンスへのアクセス

Monologは、ログに利用できる多くのハンドラーを持っています。もしそれらが必要な場合は、以下でMonologのインスタンスにアクセスできます。

$monolog = Log::getMonolog();


以上です。