Laravel5.4 ログ周りの設定 3/3 ミリ秒に出力場所を添えて

#概要
ログ周りの設定レシピ、３品。今回はその3品目。

- [Laravel5.4 ログ周りの設定 1/3 開始・終了ログを残す](http://qiita.com/n-oota/items/7901bd6ad24cc09c3fe5)
- [Laravel5.4 ログ周りの設定 2/3 クエリログを別ファイルで](http://qiita.com/n-oota/items/d2a05e967fa01743f103)
- Laravel5.4 ログ周りの設定 3/3 ミリ秒に出力場所を添えて（本記事）


#やりたいこと

- ログ出力日時をミリ秒まで表示
- ログ出力場所を表示（メソッド名・行数）
- プロセスIDもついでに表示。

```text:storage\log\app.log
[2017-06-19 12:24:42.988618] local.INFO: -- Startup {"method":"GET","uri":"/"} {"pid":2310,"line":"App\\Bootstrap\\ApplicationLog->startupLog:68"}
[2017-06-19 12:24:43.064103] local.DEBUG: Hello Log Sample  {"pid":2310,"line":"App\\Http\\Controllers\\HomeController->index:12"}
[2017-06-19 12:24:43.073957] local.INFO: -- Shutdown {"time":"108.714[ms]","memory":"2048[kb]"} {"pid":2310,"line":"App\\Bootstrap\\ApplicationLog->handleShutdownLog:84"}
```

#環境
Laravel 5.4.25
PHP 7.1.3

#実装手順
1. Illuminate\Log\Writerクラスを継承したクラスを作成し、getDefaultFormatterメソッドを上書き
2. Monolog用プロセッサ作成（必要なら。標準で同機能のものがあります。）
3. Writerクラス差し替え & Monolog用プロセッサを登録

以下、サンプル
前回の続きで、LogServiceProviderを差し替えた前提です。

一応動作確認はしていますが、サンプルをそのまま使う場合は自己責任で。

##Writerクラス作成

```php:app\Utils\Monolog\Writer.php
<?php

namespace App\Utils\Monolog;

use Illuminate\Log\Writer as BaseWriter;
use Monolog\Formatter\LineFormatter;

/**
 * Monolog用Writer
 * 日時をマイクロ秒まで表示
 *
 * @package app.Utils.Monolog
 */
class Writer extends BaseWriter
{

    /**
     * Get a default Monolog formatter instance.
     *
     * @return \Monolog\Formatter\LineFormatter
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter(null, 'Y-m-d H:i:s.u', true, true);
    }
}
```

##Monolog用プロセッサ作成（出力場所）

Monologで用意されているものを少し改良。
file, line, class, functionの４つに分けて表示されるのは見づらかったため、簡易表示版。

```php:app\Utils\Monolog\Processor\IntrospectionProcessor.php
<?php

namespace App\Utils\Monolog\Processor;

use Monolog\Logger;

/**
 * Monolog用プロセッサ
 * 標準IntrospectionProcessorの簡略版。
 * ログ出力している場所を出力します。
 *
 * @package app.Utils.Monolog.Processor
 */
class IntrospectionProcessor
{
    private $level;

    private $skipClassesPartials;

    public function __construct($level = Logger::DEBUG, array $skipClassesPartials = ['Monolog\\', 'Illuminate\\'])
    {
        $this->level = Logger::toMonologLevel($level);
        $this->skipClassesPartials = $skipClassesPartials;
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $trace = debug_backtrace();

        // skip first since it's always the current method
        array_shift($trace);
        // the call_user_func call is also skipped
        array_shift($trace);

        $i = 0;

        while (isset($trace[$i]['class'])) {
            foreach ($this->skipClassesPartials as $part) {
                if (strpos($trace[$i]['class'], $part) !== false) {
                    $i++;
                    continue 2;
                }
            }
            break;
        }

        $line = null;
        if (isset($trace[$i]['class'])) {
            $line = $trace[$i]['class'];
            if (isset($trace[$i]['class'])) {
                $line = $line . '->' . $trace[$i]['function'];
            }
        } else if (isset($trace[$i - 1]['file'])) {
            $line = $trace[$i - 1]['file'];
        }
        if (null !== $line && isset($trace[$i - 1]['line'])) {
            $line = $line . ':' . $trace[$i - 1]['line'];
        }

        $record['extra']['line'] = $line;

        return $record;
    }
}
```

##Monolog用プロセッサ作成（プロセスID）

Monologで用意されているものと同じです。
キーの名前を省略したかっただけです。（process_id > pid）

```php:app\Utils\Monolog\Processor\IntrospectionProcessor.php
<?php

namespace App\Utils\Monolog\Processor;

/**
 * Monolog用プロセッサ
 * 標準ProcessIdProcessorの出力キーの文字数が長いため、簡略版
 *
 * @package app.Utils.Monolog.Processor
 */
class ProcessIdProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $record['extra']['pid'] = getmypid();

        return $record;
    }
}
```

##Writer差し替え & Monolog用プロセッサ登録

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

namespace App\Providers;

// 差し替え
//use Illuminate\Log\Writer;
use App\Utils\Monolog\Writer;

// 追加
use App\Utils\Monolog\Processor\IntrospectionProcessor;
use App\Utils\Monolog\Processor\ProcessIdProcessor;

use Carbon\Carbon;
use Illuminate\Support\ServiceProvider;
use Monolog\Logger as Monolog;

/**
 * ロガー登録プロバイダ
 *
 * @package app.Providers
 */
class LogServiceProvider extends ServiceProvider
{

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('log', function () {
            return $this->createAppLogger();
        });
        $this->app->singleton('sql-log', function () {
            return $this->createSqlLogger();
        });
    }

    /**
     * Create the app logger.
     *
     * @return \Illuminate\Log\Writer
     */
    public function createAppLogger()
    {
        // プロセッサーを登録
        $processors = [
            new ProcessIdProcessor(),
            new IntrospectionProcessor()
        ];
        $log = new Writer(
            new Monolog($this->channel(), [], $processors), $this->app['events']
        );
        $this->configureHandler($log, 'app');
        return $log;
    }

    /**
     * Create the sql logger.
     *
     * @return \Illuminate\Log\Writer
     */
    public function createSqlLogger()
    {
        // プロセッサーを登録
        $processors = [
            new ProcessIdProcessor(),
        ];
        $log = new Writer(
            new Monolog($this->channel(), [], $processors), $this->app['events']
        );
        $this->configureHandler($log, 'sql');
        return $log;
    }

    //以下、省略

```

#さいごに

３回に分けて、よくやってるログ設定をまとめてみました。
ログ大事です。何かあった時に調査しやすいようログを適切に書きましょう。（自分に言い聞かせてる）

以上です。