Laravelスケジュールの実行ログを出力する

## 概要
Laravelでスケジュール機能を使って定期実行する際、実行ログを出力したいといったことがあったので調べたことをメモしておく。

## ファイル構成
こんな感じでdockerのcron環境を整備する。
※ ソースコード： [Github-reflet/laravel5.6](https://github.com/reflet/laravel5.6)

```
├ docker                          // ← docker関連のフォルダ
│ └ cron
│   ├ cron.root
│   ├ Dockerfile                  // ← cron実行するコンテナ (PHP-CLI)
│   └ php.ini
│
├ src                             // ← Laravel関連のフォルダ
│ ├ app
│ │ ├ Console
│ │ │  ├ Commands
│ │ │  │  ├ EmailSendCommand.php  // ← メールをテスト送信するコマンド 
│ │ │  │  └ Schedule.php          // ← schedule:runコマンド を上書きする
│ │ │  └ Kernel.php               // ← スケジュール設定
│
│   ...
│
│ ├ resources
│ │  └ views
│ │    └ emails
│ │      └ test.blade.php         // ← テストメール文面
│
│   ...
│
│ └ yarn.lock
│
└ docker-compose.yml 
```

## コマンド作成
テストメールを送信するプログラムを追加する。

```bash
$ docker-compose exec php php artisan make:mail TestEMail
```
```php:./src/app/Mail/TestEMail.php
    protected $body;

    public function __construct(string $body = '')
    {
        $this->body = $body;
    }

    public function build(): TestEMail
    {
        return $this
            ->from('hoge@example.com')
            ->view('emails.test')
            ->with([
                'body' => $this->body
            ]);
    }
```
定期実行するテストメール送信コマンドを追加する。

```bash
$ docker-compose exec php php artisan make:command EmailSendCommand
```
```php:./src/app/Console/Commands/EmailSendCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
use App\Mail\TestEMail;

class EmailSendCommand extends Command
{
    protected $signature = 'email:test';

    ...

    public function handle()
    {
        $this->info(sprintf(
            '[%s] email:test command start.',
            Carbon::now()->format('Y-m-d H:i:s')
        ));

        $message = 'バッチ実行だよ。';
        Mail::to('hoge@example.com')
            ->send(new TestEmail($message));

        $this->info(sprintf(
            '[%s] email:test command complete.',
            Carbon::now()->format('Y-m-d H:i:s')
        ));
    }
}
```

## スケジュール設定
Kernel.phpに設定を追加する。

```php:./src/app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        // ↓ 実行環境 ※ ローカルを除外する ( envファイルで実行対象に... )
        $env = array_map('trim', explode(',', env('SCHEDULE_ENVS', 'staging,production')));

        // ↓ 1時間ごとに定期実行する
        $schedule
            ->command('email:test')   // 実行するコマンド
            ->everyMinute()           // 実行間隔: 毎分
            ->environments($env)      // ローカル環境でのみ実行してみる
        ;
    }
```

## logging設定
ログに`schedule`チャンネルを新規に追加する。

```php:./src/config/logging.php
    'channels' => [
        ...

        // ↓ これの設定を追加する
        'schedule' => [
            'driver'     => 'daily',
            'path'       => storage_path('logs/schedule.log'),
            'level'      => 'info',
            'permission' => 0666,
            'days'       => 7,
        ],

        ...

    ]
```

## schedule:runコマンド
Laravelの `$ php artisan schedule:run` コマンドを上書きする。

```php:./src/app/Console/Commands/Schedule.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Scheduling\ScheduleRunCommand;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\Facades\Log;

class Schedule extends ScheduleRunCommand
{
    public function __construct(\Illuminate\Console\Scheduling\Schedule $schedule)
    {
        parent::__construct($schedule);
    }

    protected function runEvent($event)
    {
        // ↓ スケジュール実行ログを出力する
        Log::channel('schedule')
            ->info(sprintf('Running scheduled command: %s', 
                $event->getSummaryForDisplay()
            ));

        // ↓ スケジュール実行結果ログを出力する
        $handlers = Log::channel('schedule')->getLogger()->getHandlers();
        foreach ($handlers as $handler) {
            // dailyのログローテートしているので、そのハンドラーから情報取得する
            if ($handler instanceof \Monolog\Handler\RotatingFileHandler) {
                // ログのパスを取得する
                $path = $handler->getUrl();
                $event->appendOutputTo($path);  // 実行結果をログ出力する
            }
        }

        // ↓ 実行環境 (検証・本番)
        // ※ ここでセットすると実行コマンドが無いときに何もログ出力されないので辞める...
        # $env = array_map('trim', explode(',', env('SCHEDULE_ENVS', 'staging,production')));
        # $event->environments($env);

        parent::runEvent($event);
    }
}
```

## 動作確認
cronの実行状況を確認してみる。

```bash
$ docker-compose logs cron
cron_1     | Running scheduled command: '/usr/local/bin/php' 'artisan' email:test >> '/var/www/www.example.com/storage/logs/schedule-2021-06-23.log' 2>&1
```

ログの出力状況を確認してみる。

```:./src/storage/logs/schedule-2021-06-23.log
[2021-06-23 11:22:01] local.INFO: Running scheduled command: '/usr/local/bin/php' 'artisan' email:test >> '/var/www/www.example.com/storage/logs/schedule-2021-06-23.log' 2>&1  
[2021-06-23 11:22:02] email:test command start.
[2021-06-23 11:22:02] email:test command complete.
```

問題ないようです。

## 参考サイト
* [Laravel 5.6 タスクスケジュール](https://readouble.com/laravel/5.6/ja/scheduling.html)
* [Laravel 5.6 Artisanコンソール](https://readouble.com/laravel/5.6/ja/artisan.html)
* [Laravel 5.6 メール](https://readouble.com/laravel/5.6/ja/mail.html)

以上