Laravelでログ出力を行う

# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています


# Controllerにメソッド追加
(1) /sample/app/Http/Controllers/SampleController.phpに下記を追記
`use Illuminate\Support\Facades\Log;`

(2) /sample/app/Http/Controllers/SampleController.phpにlogメソッドを追記

```
    public function log(Request $request)
    {
        Log::emergency('ログサンプル', ['memo' => 'sample1']);
        Log::alert('ログサンプル', ['memo' => 'sample1']);
        Log::critical('ログサンプル', ['memo' => 'sample1']);
        Log::error('ログサンプル', ['memo' => 'sample1']);
        Log::warning('ログサンプル', ['memo' => 'sample1']);
        Log::notice('ログサンプル', ['memo' => 'sample1']);
        Log::info('ログサンプル', ['memo' => 'sample1']);
        Log::debug('ログサンプル', ['memo' => 'sample1']);

        return view('sample.log');
    }

```

Logクラスのログ出力メソッド名はログレベルに相当します
emergencyは最も重要度が高く、debugは最も重要度が低いです

(2) /sample/routes/web.phpに下記を追記
`Route::get('sample/log', 'SampleController@log');`

# viewの作成
 /sample/resources/views/sample/log.blade.phpファイル作成

```log.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>
        <div>log</div>
    </body>
</html>

```

# logの設定
(1) /sample/config/logging.phpファイル修正
logging.phpは配列を返すファイルになっています
default要素の値が使用するログチャンネルとなります
ログ出力の際には、どこ(ファイル、DB、メール等)に何(ファイル名、行番号、リクエストURL等)を、どのように(日時の形式等)出力するかを決めなければなりません
どこ、何、どのようにの組み合わせを定義したものをログチャンネルと呼ぶと考えて差し支えないと思います
default要素はenv('LOG_CHANNEL', 'stack')となっていると思います。つまりstackチャンネルを使用します

stackチャンネルはどのような定義になっているか見ていきます

logging.phpファイルのchannels要素に様々なログチャンネルが定義されています
'channels'=>'stack'要素がstackチャンネルです
'channels'=>'stack'要素(stackチャンネル)を修正します

```
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'ignore_exceptions' => false,
            'channels' => ['single', 'daily'],
            'name' => 'stack-channel',
        ],
　　　　　‥‥
```
channels要素を修正し、name要素を追加しました
stackチャンネルというのは複数のログチャンネルを1つにまとめたものです。したがって、使用するログチャンネルを'channels'=>'stack'=>'channels'要素に書くだけです
今回はsingleチャンネルとdailyチャンネルを使用する設定にしました
name要素はチャンネル名です。お好きな名前を設定してください

では、'channels'=>'single'要素(singleチャンネル)を修正します

```
    'channels' => [
　　　　　‥‥
        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'tap' => [App\Logging\SampleTap::class],
        ],
　　　　　‥‥
```
singleチャンネルは1つのファイルにログを出力するチャンネルです。
level要素はどのログレベル以上をログファイルに出力するかを設定しています。例えば、infoと設定した場合、infoより重要度の低いdebugはメソッドが呼ばれたとしてもログに出力されません
path要素でstorage/logs/laravel.logにログを出力する設定になっています。これでどこにログを出力するかは決まりました
次に、何を、どのように出力するかを決めます。それがtap要素です。tap要素にはSampleTapクラスを設定しました
これからSampleTapクラスを作成します

(2) Tapクラスの修正
/sample/app/Loggingフォルダ作成
/sample/app/Logging/SampleTap.phpファイル作成

```SampleTap.php
<?php

namespace App\Logging;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Processor\HostnameProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\IntrospectionProcessor;
use App\Logging\SampleProcessor;

class SampleTap
{
    const FORMAT = "[%datetime%] %level_name% %channel% %message% %extra.file% %extra.line% %extra.class% %extra.function% %extra.hostname% %extra.url% %extra.ip% %extra.http_method% %extra.server% %extra.referrer% %extra.custom% %context.memo%\n";

    public function __invoke($logger)
    {
        $hostnameProcessor = new HostnameProcessor();
        $webProcessor = new WebProcessor();
        $introspectionProcessor = new IntrospectionProcessor(Logger::DEBUG, ['Illuminate\\']);
        $sampleProcessor = new SampleProcessor();

        $lineFormatter = new LineFormatter(static::FORMAT);

        foreach ($logger->getHandlers() as $handler) {
            $handler->pushProcessor($webProcessor);
            $handler->pushProcessor($hostnameProcessor);
            $handler->pushProcessor($introspectionProcessor);
            $handler->pushProcessor($sampleProcessor);
            $handler->setFormatter($lineFormatter);
        }
    }
}
```
pushProcessorに渡しているのが何を出力するか
setFormatterに渡しているのがどのようにを出力するか
を定義しているクラスです

まずはProcessorを見ていきましょう
Laravelのログ出力は[Monolog](https://github.com/Seldaek/monolog)を使用しています
したがってMonologのクラスを使用していきます
HostnameProcessor、WebProcessor、IntrospectionProcessorはMonologが提供してくれているクラスです
他にもProcessorクラスを提供してくれています。
[Monologが提供しているProcessor](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors)
HostnameProcessorはホスト名を出力してくれます
WebProcessorはリクエストURI、リクエストメソッド、クライアントIP、サーバ名、リファラを出力してくれます
IntrospectionProcessorはファイル、行番号、クラス、関数名を出力してくれます
Monologが提供しているProcessorでは出力されない値をログに出力したい場合、自分でProcessorクラスを作成することになります
SampleProcessorクラスは今回自作したProcessorです。後述します

つぎにFormatterを見ていきましょう
LineFormatterはMonologが提供してくれているクラスです
他にもFormatterクラスを提供してくれています。
[Monologが提供しているFormatter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#formatters)
LineFormatterはコンストラクタの第一引数に与えられた文字列に従ってフォーマットしてくれます
今回はSampleTapクラスのFORMAT定数を与えています
LineFormatterは%で囲われた文字列を下記の通りに置換します
%datetime%：ログ出力の日時
%level_name%：ログレベル。Log::error関数でログ出力した場合はerrorがログレベルです
%channel%：logging.phpファイルの'channels'=>'stack'=>'name'
%message%：ログ出力関数の第一引数。Log::error('ログサンプル', ['memo' => 'sample1']);とログ出力した場合、'ログサンプル'
%context.xxx%：ログ出力関数の第二引数。Log::error('ログサンプル', ['memo' => 'sample1']);とログ出力した場合、%context.memo%が'sample1'に置換される
%extra.xxx%：Processorクラスで出力する値。あとでSampleProcessorを作るときに説明します

Formatterを自作することもできますが、ほとんどの場合、Monologが提供しているFormatterで十分でしょう
もし、Formatterを自作する場合は、Monolog\Formatter\FormatterInterfaceをimplementsしたクラスを作ってください


(3) Processorクラスの修正
自作のProcessorクラスをつくります
/sample/app/Logging/SampleProcessor.phpファイル作成

```SampleProcessor.php<?php

namespace App\Logging;

use Monolog\Processor\ProcessorInterface;

class SampleProcessor implements ProcessorInterface
{
    public function __invoke(array $record)
    {
        $record['extra']['custom'] = 'sample_extra';
        return $record;
    }
}

```

`$record['extra']['custom']`に値を入れて返すと
フォーマット文字列内の%extra.custom%部分が$record['extra']['custom']の値に置換されます
第一要素を'extra'とするのはProcessorを使う上での決まりです
先ほど使用したMonologが提供しているProcessorが置換するのは下記です
HostnameProcessor：extra.hostname
WebProcessor：extra.url、extra.ip、extra.http_method、extra.server、extra.referrer
IntrospectionProcessor：extra.file、extra.line、extra.class、extra.function

(4) 再び/sample/config/logging.phpファイル修正
今度は、'channels'=>'daily'要素(dailyチャンネル)を修正します

```
    'channels' => [
　　　　　‥‥
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
            'days' => 0,
            'tap' => [App\Logging\SampleTap::class],
        ],
　　　　　‥‥
```
dailyチャンネルは日付毎に1つのファイルにログを出力するチャンネルです。
どこに出力するか：path要素でstorage_path('logs/laravel.log')となっている場合、laravel-YYYY-MM-DD.logというファイル名形式のファイルが日付ごとにできます
何を、どのように出力するか：先ほどsingleチャンネルに設定したのと同じSampleTapクラスを設定しました
days要素は最大何日分のログファイルを保存しておくかです。0の場合、制限無しです

# タイムゾーン設定
 /sample/config/app.php修正

```app.php
‥‥
'timezone' => 'Asia/Tokyo',
‥‥
```
timezone要素がUTCになっていると思うので、自分のタイムゾーンに変更します
これによりログに出力される日時、dailyチャンネルのファイル名が自分のタイムゾーンに合ったものになります

# 動作確認
`http://localhost/laravelSample/sample/log`

storage/logs配下を見るとログファイルができており、ログが出力されています

# 他のログチャンネル
Laravelには他にもログチャンネルが用意されています
下記リンク先の利用可能なチャンネルドライバという箇所に使用できるログチャンネル一覧があります
[Laravel ログ](https://readouble.com/laravel/7.x/ja/logging.html)
また、config/logging.php内のchannels配列を見ても使えるログチャンネルを確認できます
これらのログチャンネルを使うためのlogging.phpでの設定は
vendor/laravel/framework/src/Illuminate/Log/LogManager.phpのcreateチャンネル名Driverという名前のメソッド(createSlackDriverとかcreateSyslogDriverとか)を見るとわかります。
それらのメソッドは`$config`を引数に取り、MonologのHandlerクラスのコンストラクタに`$config`の要素を渡しています
MonologのHandlerクラスのコンストラクタに渡している`$config`の要素をlogging.phpで定義すれば使うことができます
MonologのHandlerは下記で確認できます
[MonologのHandler](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#handlers)