Laravelバッチ(コマンド) のログをバッチ毎にCloudWatch Logsへ出力する

## はじめに
EC2上でLaravel APIサーバーが稼働しています。
このAPIサーバーでは`$schedule->command`を使用して定期的にバッチ(コマンド)の実行もしています。
（バッチサーバーも兼ねているわけです。）

APIとバッチがあいのりしている件はあまり良くないですが、今回はその改善には触れません。

今回の問題はログです。
`storage/logs`配下へログを出力していますが、
1つのログファイルにAPIとバッチのログがまじってしまっており、さらにバッチが10個程度存在していたため、
どれがどのログなのかを理解する・探すのも難しい状態になっていました。

そこで各ログファイルを分け、ついでにCloudWatchLogsへ連携してみました。

## バッチごとにログを分ける
### logging.phpへチャンネルの設定を追加する 
バッチごとに使用するログチャンネルの設定を記載します。
日毎にローテーションしていたり、debugログすべて出力していたりするのでそこは調整してください。

```php:config/logging.php
~省略~
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily'],
            'ignore_exceptions' => false,
        ],

        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'permission' => 0777,
        ],

        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => 30,
            'permission' => 0777
        ],

        // バッチごとにログファイルを分けるためチャンネルを作成
        'batch-a' => [
            'driver' => 'daily',
            'path' => storage_path('logs/batch-a.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => 30,
            'permission' => 0777,
            'ignore_exceptions' => false
        ],
        'batch-b' => [
            'driver' => 'daily',
            'path' => storage_path('logs/batch-b.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => 30,
            'permission' => 0777,
            'ignore_exceptions' => false
        ],
        'batch-c' => [
            'driver' => 'daily',
            'path' => storage_path('logs/batch-c.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => 30,
            'permission' => 0777,
            'ignore_exceptions' => false
        ],
    ]
~省略~
```

```php:BatchA.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class BatchA extends Command
{
~省略~
    public function handle()
    {
        Log::setDefaultDriver('batch-a');
        
        処理
    }
}

```
これでBatchAからのログ出力にはbatch-aチャンネルが利用され`logs/batch-a.log`へログが出力されます。
※BatchB、Cも同様です。

下記のようにログ出力するたびにチャンネルを指定する方法もあるようですが、
いちいちチャンネルを指定するのは面倒だったので上記のように`setDefaultDriver`を利用しています。

```php
Log::channel('batch-a')->info('Something happened!');
```

また、下記のようにすれば`logging.php`へ設定を行わなくてもログの設定ができるようですが、
この設定を`setDefaultDriver`のように全体に反映する方法が見つからなかったため断念しました。
```php
Log::build([
    'driver' => 'daily',
    'path' => storage_path('logs/batch-a.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 30,
    'permission' => 0777,
    'ignore_exceptions' => false
])->info('Something happened!');
```

## CloudWatchエージェントを使用してEC2からログを送る
ここまででログの分離は完了したので、そのログをCloudWatch Logsへ連携していきます。

### EC2のロールへ「CloudWatchLogsFullAccess」ポリシーを追加
対象となるインスタンスがCloudWatch Logsにアクセス可能とするために、EC2に割り当てるIAMロールを作成します。
[こちら](https://business.ntt-east.co.jp/content/cloudsolution/column-try-28.html)を参考に行いました。

### EC2へssh接続しCloudWatchエージェントをインストール
以下のコマンドを実行しCloudWatchエージェントをインストールします。
```
$ wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
$ sudo dpkg -i -E ./amazon-cloudwatch-agent.deb
$ sudo amazon-cloudwatch-agent-ctl -a start
```

### CloudWatchエージェントの定義ファイルを編集
ログファイルを転送する設定を書いていきます。
```
$ sudo vi /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.d/default
```

バッチのログだけでなく、API側のログやnginxのログの転送設定も行いました。
```json
{
    "agent": {
        "run_as_user": "root"
    },
    "logs": {
        "logs_collected": {
            "files": {
                "collect_list": [
                    {
                        "file_path": "/アプリのパス/storage/logs/laravel*log",
                        "log_group_name": "api-laravel-log",
                        "log_stream_name": "ec2-{instance_id}-laravel-log"
                    },
                    {
                        "file_path": "/アプリのパス/storage/logs/batch-a*log",
                        "log_group_name": "batch-laravel-log",
                        "log_stream_name": "ec2-{instance_id}-batch-a-log"
                    },
                    {
                        "file_path": "/アプリのパス/storage/logs/batch-b*log",
                        "log_group_name": "batch-laravel-log",
                        "log_stream_name": "ec2-{instance_id}-batch-b-log"
                    },
                    {
                        "file_path": "/アプリのパス/storage/logs/batch-c*log",
                        "log_group_name": "batch-laravel-log",
                        "log_stream_name": "ec2-{instance_id}-batch-c-log"
                    },
                    {
                        "file_path": "/var/log/nginx/api.log",
                        "log_group_name": "api-nginx-log",
                        "log_stream_name": "ec2-{instance_id}-nginx-log"
                    },
                    {
                        "file_path": "/var/log/nginx/api-error.log",
                        "log_group_name": "api-nginx-error-log",
                        "log_stream_name": "ec2-{instance_id}-nginx-error-log"
                    },
                    {
                        "file_path": "/var/log/php7.4-fpm.log",
                        "log_group_name": "php-fpm-log",
                        "log_stream_name": "ec2-{instance_id}-php-fpm-log"
                    }
                ]
            }
        }
    }
}
```

### CloudWatchエージェント再起動,ステータス確認
```
$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a stop
$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -m ec2 -a start
```

### ログがCloudWatchLogsに転送されているか確認

AWSマネージメントコンソールへサインインし、
CloudWatch Logs > ロググループ > batch-laravel-log を確認。
バッチごとにLog Streamが別れていることが確認できました！

## 最後に
定義ファイルをバッチごとに書かなくてはならないので、
1つの定義でいい感じに設定する方法を模索中。
知っている方いたら是非教えて下さい。