Laravelのログ出力先とフォーマットの変更

## 概要
Laravelのアプリケーションログの出力先を.envで変更できるようにして、さらにフォーマットも変更したかったので調べた内容をメモしておく。

## ログのフォーマットについて
いろいろとありそうですが、LTSV形式が使いやすそうだったので使ってみる。
自分でMonologのFormatterを作るのも面倒なのでGoogle先生に聞いたところ良さそうのが見つかる。

**■ hikaeme/monolog-ltsv-formatter**
https://packagist.org/packages/hikaeme/monolog-ltsv-formatter

上記ライブラリを利用することにします。

```bash
# インストール
$ composer require hikaeme/monolog-ltsv-formatter
```
## カスタマイズ方法について検討
Laravelのログ出力のカスタマイズを調べてみるとマニュアルには、

https://readouble.com/laravel/5.5/ja/errors.html

configureMonologUsingメソッドでと紹介されているが書いてみると・・・イマイチ微妙。

```php:bootstrap/app.php
$app->configureMonologUsing(function($monolog) {  
    $level = \Monolog\Logger::DEBUG;  
    $format = "%datetime%,%level_name%,%message%,%context%,%extra%\n";

    $monolog->pushProcessor(new \Monolog\Processor\IntrospectionProcessor(
        $level, ['Monolog\\', 'Illuminate\\',]
    ));

    $monolog->pushHandler(
        $handler = new \Monolog\Handler\StreamHandler(
            storage_path('logs/laravel.log'), 
            $level
        )
    );
    $handler->setFormatter(new Monolog\Formatter\LineFormatter($format));
});
```

**<微妙と思う点について>**

- config/app.phpにログの設定があるが、上記方法でカスタマイズすると設定が反映されない
- ログレベルなどの設定も.envやconfigの設定が反映されない(できなくもないが・・・)

他の方法を探すと「LogServiceProvider」の差し替えで行けそう。

**■Laravel・Lumenでログ出力先を変更する**
https://k2ss.info/archives/1629

こちらの方法を採用してみる。

## LogServiceProviderの実装
Illuminate\Log\Writerの「 parseLevel 」メソッドが、protectedで利用できず、仕方がなく移植。

```php:app/Providers/LogServiceProvider.php
<?php

namespace App\Providers;

use InvalidArgumentException;
use Illuminate\Log\LogServiceProvider AS BaseLogServiceProvider;
use Illuminate\Log\Writer;
use Monolog\Logger AS MonologLogger;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Hikaeme\Monolog\Formatter\LtsvFormatter;

class LogServiceProvider extends BaseLogServiceProvider
{
    /**
     * The Log levels.
     * ※「Illuminate\Log\Writer」から移植
     *
     * @var array
     */
    protected $levels = [
        'debug'     => MonologLogger::DEBUG,
        'info'      => MonologLogger::INFO,
        'notice'    => MonologLogger::NOTICE,
        'warning'   => MonologLogger::WARNING,
        'error'     => MonologLogger::ERROR,
        'critical'  => MonologLogger::CRITICAL,
        'alert'     => MonologLogger::ALERT,
        'emergency' => MonologLogger::EMERGENCY,
    ];

    /**
     * singleモードの出力
     *
     * @param  \Illuminate\Log\Writer $log
     * @return void
     * @throws \Exception
     */
    protected function configureSingleHandler(Writer $log)
    {
        // コメントアウト (フォーマットが変更できない)
        #$log->useFiles(
        #    $this->app->storagePath().'/logs/laravel.log',
        #    $this->logLevel()
        #);

        $monolog = $log->getMonolog();

        // フォーマット変更 (LTSV形式)
        $monolog->pushHandler(
            $handler = new StreamHandler(
                $this->path().$this->file(),
                $this->parseLevel($this->logLevel())
            )
        );
        $handler->setFormatter(new LtsvFormatter());

        // ログ追加 (ファイル名、行番号、クラス名、メソッド名)
        $monolog->pushProcessor(
            new IntrospectionProcessor(MonologLogger::DEBUG, ['Monolog\\', 'Illuminate\\',])
        );
    }

    /**
     * 日付ローテートモードでの出力
     *
     * @param  \Illuminate\Log\Writer $log
     * @return void
     * @throws \Exception
     */
    protected function configureDailyHandler(Writer $log)
    {
        // コメントアウト (フォーマットが変更できない)
        #$log->useDailyFiles(
        #    $this->app->storagePath().'/logs/laravel.log', $this->maxFiles(),
        #    $this->logLevel()
        #);

        $monolog = $log->getMonolog();

        // フォーマット変更 (LTSV形式)
        $monolog->pushHandler(
            $handler = new RotatingFileHandler(
                $this->path().$this->file(),
                $this->maxFiles(),
                $this->parseLevel($this->logLevel())
            )
        );
        $handler->setFormatter(new LtsvFormatter());

        // ログ追加 (ファイル名、行番号、クラス名、メソッド名)
        $monolog->pushProcessor(
            new IntrospectionProcessor(MonologLogger::DEBUG, ['Monolog\\', 'Illuminate\\',])
        );
    }

    /**
     * ログ出力先
     *
     * @return string
     */
    private function path(): string
    {
        return env('APP_LOG_DIR', $this->app->storagePath().'/logs/');
    }

    /**
     * ファイル名
     *
     * @return string
     */
    private function file(): string
    {
        return 'laravel.log';
    }

    /**
     * Parse the string level into a Monolog constant.
     * ※「Illuminate\Log\Writer」から移植
     *
     * @param  string  $level
     * @return int
     *
     * @throws \InvalidArgumentException
     */
    protected function parseLevel($level): int
    {
        if (isset($this->levels[$level])) {
            return $this->levels[$level];
        }

        throw new InvalidArgumentException('Invalid log level.');
    }
}

```

## 独自のApplicationクラス
LogServiceProviderを差し替えるために、独自クラスを作成する。

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

namespace App;

use App\Providers\LogServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;

/**
 * アプリケーションクラス
 * ※ ログサービスプロバイダを変更したいため、overwrite
 *
 * @package app.Providers
 */
class Application extends \Illuminate\Foundation\Application
{
    /**
     * Register all of the base service providers.
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));

        $this->register(new LogServiceProvider($this));

        $this->register(new RoutingServiceProvider($this));
    }
}

```

## bootstrap/app.php
上記で作成した独自Applicationクラスに差し替えるため、修正。

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

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/

// Applicationクラスを差し替え
// $app = new Illuminate\Foundation\Application(
$app = new \App\Application(
    realpath(__DIR__.'/../')
);

```

## 試してみる
クエリの実行ログを出力してみる。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // アプリケーションで実行される各SQLクエリのログ出力
        DB::listen(function ($query) {
            // 改行と連続スペースを取り除く
            $sql = trim(preg_replace('/\s+/', ' ', str_replace(["\r\n", "\r", "\n"], '', $query->sql)));
            \Log::debug($sql);
            \Log::debug($query->bindings);
            \Log::debug($query->time);
        });
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

```

実行してみると、下記のようにLTSV形式のログが出力される。

```ltsv:storage/logs/laravel.php
time:2018-07-12 12:13:48	level:DEBUG	message:SELECT id, name FROM users WHERE id = ? ;	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:21	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
time:2018-07-12 12:13:48	level:DEBUG	message:array (\n  0 => '1',\n)	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:22	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
time:2018-07-12 12:13:48	level:DEBUG	message:16.79	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:23	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
```

ログの出力場所を変えてみる。
パーミッションの関係で他のところに出力し難かったので、とりあえず、storageフォルダ直下に出力してみる。

.envファイルで出力先のパスを指定する。

```env:.env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:*********************************=
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_LOG_DIR=/var/www/www.example.com/storage/     <- ここに追加
APP_URL=http://localhost

```

実行してみると、下記のようにLTSV形式のログが出力される。

```ltsv:storage/laravel.php
time:2018-07-12 12:13:48	level:DEBUG	message:SELECT id, name FROM users WHERE id = ? ;	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:21	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
time:2018-07-12 12:13:48	level:DEBUG	message:array (\n  0 => '1',\n)	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:22	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
time:2018-07-12 12:13:48	level:DEBUG	message:16.79	file:/var/www/www.example.com/app/Providers/AppServiceProvider.php	line:23	class:App\Providers\AppServiceProvider	function:App\Providers\{closure}
```

以上