[Laravel 5.8] report()にコンテキストを渡せるようにする

# report()? コンテキスト？

Laravelが用意しているreportヘルパは、例外をロギングしてくれます。

```php
report(new \Exception('sample exception'));
```

```:ログファイル
[2019-07-30 16:15:55] local.ERROR: sample exception {"exception":"[object] (Exception(code: 0): sample exception at /var/www/routes/web.php:29)
[stacktrace]
...
```

ログファイルにある`{"exception":"[object] ...`がコンテキストで、ロギングする情報のことです。デフォルトでは例外情報とログインしていればそのユーザーIDになります。以下はデフォルトLaravelの実装です。

```php:vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
    /**
     * Report or log an exception.
     *
     * @param  \Exception  $e
     * @return mixed
     *
     * @throws \Exception
     */
    public function report(Exception $e)
    {
        //略

        $logger->error(
            $e->getMessage(),
            array_merge($this->context(), ['exception' => $e] // ここでコンテキストをロギング
        ));
    }

    /**
     * Get the default context variables for logging.
     *
     * @return array
     */
    protected function context()
    {
        try {
            return array_filter([
                'userId' => Auth::id(),
                // 'email' => optional(Auth::user())->email,
            ]);
        } catch (Throwable $e) {
            return [];
        }
    }
```

上記のcontextメソッドをオーバーライドすればコンテキストを自由に編集できますが、全てのreportヘルパに影響するので、reportするときにコンテキストを渡せるようにしました。

ただ、本当は以下のようにしたかったのですが

```php:こうしたい
report($e); // デフォルトのコンテキストでロギング
report($e, ['hoge' => 'fuga']); // コンテキストに追加してロギング
```

後述の理由で難しそうだったので以下で妥協しました。

```php:こうなった
use Illuminate\Contracts\Debug\ExceptionHandler;

report($e); // デフォルトのコンテキストでロギング
app(ExceptionHandler::class)->report($e, ['hoge' => 'fuga']); // コンテキストに追加してロギング
```

# 実装

`app/Exceptions/Handler.php`でreportメソッドをオーバーライドします。

```php:app/Exceptions/Handler.php
    /**
     * Report or log an exception.
     *
     * @param  \Exception  $e
     * @return mixed
     *
     * @throws \Exception
     */
    public function report(Exception $e, array $context = []) // $contextを引数に追加
    {
        if ($this->shouldntReport($e)) {
            return;
        }

        if (is_callable($reportCallable = [$e, 'report'])) {
            return $this->container->call($reportCallable);
        }

        try {
            $logger = $this->container->make(LoggerInterface::class);
        } catch (Exception $ex) {
            throw $e;
        }

        $logger->error(
            $e->getMessage(),
            array_merge($this->context(), $context, ['exception' => $e] // $contextをマージするようにする
        ));
    }
```

使用するときは、サービスコンテナからExceptionHandlerを受け取りreportしてください。

```php
use Illuminate\Contracts\Debug\ExceptionHandler;

app(ExceptionHandler::class)->report(new \Exception('sample exception'), ['hoge' => 'fuga']);
```

```:ログファイル
[2019-07-30 16:34:17] local.ERROR: sample exception {"hoge":"fuga","exception":"[object] (Exception(code: 0): sample exception at /var/www/routes/web.php:25)
[stacktrace]
...
```

## reportヘルパにコンテキストを渡せるようにできないか？

上記はreportヘルパを通さず、直接ExceptionHandlerのreportメソッドを呼んでいます。reportヘルパを使って`report($e, $context)`とするには、reportヘルパをオーバーライドする必要があります。
reportヘルパの実装は以下になっています。

```php:vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
if (! function_exists('report')) {
    /**
     * Report an exception.
     *
     * @param  \Throwable  $exception
     * @return void
     */
    function report($exception)
    {
        if ($exception instanceof Throwable &&
            ! $exception instanceof Exception) {
            $exception = new FatalThrowableError($exception);
        }

        app(ExceptionHandler::class)->report($exception);
    }
}
```

ヘルパのオーバーライドは、
https://qiita.com/kd9951/items/46ef3559009ee575ea7d
の記事が詳しいです。ただヘルパの引数を追加したいので、この記事のベストな方法（サービスコンテナで入れ替える）は使えません。

以下のような`app/helpers.php`を作り、`vendor/laravel/framework/src/Illuminate/Foundation/helpers.php`よりも早く`app/helpers.php`を読み込むようにすればオーバーライドできるようですが、良さそうな方法がないので辞めました。

```php:app/helpers.php
<?php

if (! function_exists('report')) {
    /**
     * Report an exception.
     *
     * @param  \Throwable  $exception
     * @param  array  $context
     * @return void
     */
    function report($exception, $context = []) // 引数に$context追加
    {
        if ($exception instanceof Throwable &&
            ! $exception instanceof Exception) {
            $exception = new FatalThrowableError($exception);
        }

        app(ExceptionHandler::class)->report($exception, $context); // $contextを渡す
    }
}
```