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


はじめに

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

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

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

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

バッチごとにログを分ける

logging.phpへチャンネルの設定を追加する

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

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
        ],
    ]
~省略~
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を利用しています。

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

また、下記のようにすればlogging.phpへ設定を行わなくてもログの設定ができるようですが、
この設定をsetDefaultDriverのように全体に反映する方法が見つからなかったため断念しました。

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ロールを作成します。
こちらを参考に行いました。

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のログの転送設定も行いました。

{
    "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つの定義でいい感じに設定する方法を模索中。
知っている方いたら是非教えて下さい。