LaravelでCloudWatchへログ送信

# はじめに
LaravelのログをCloudWatchへ送信する方法は、CloudWatchエージェントを使う方法やaws firelensを使う方法などがあります。しかし、これらの方法はAWSのリソースを作らなくてはならず、設定などが少し手間です。
今回はLaravelのソース（とIAM Userの発行）だけで完結する方法を紹介します。



# 方法

まずは、ライブラリのインストールを行います。使用するライブラリはこちらです。
https://github.com/maxbanton/cwh

```shell
$ composer require maxbanton/cwh
```


インストールしたライブラリに含まれるLogハンドラーをコンテナに登録します。ポイントは`CredentialProvider::defaultProvider()`としているところで、このようにすることで、ローカル環境でも本番環境でもよしなに認証情報を取得してくれます。

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Aws\CloudWatchLogs\CloudWatchLogsClient;
use Aws\Credentials\CredentialProvider;
use Illuminate\Support\ServiceProvider;
use Maxbanton\Cwh\Handler\CloudWatch;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(CloudWatchLogsClient::class, function ($app) {
            return new CloudWatchLogsClient([
                'region' => config('aws.default_region'),
                'version' => 'latest',
                'credentials' => CredentialProvider::defaultProvider(),
            ]);
        });

        $this->app->bind(CloudWatch::class, function ($app) {
            return new CloudWatch(
                $app->make(CloudWatchLogsClient::class),
                config('aws.cloudwatch.log.group_name'),
                config('aws.cloudwatch.log.stream_name'),
                config('aws.cloudwatch.log.retention'),
                10000
            );
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

```


続いて、configファイルの設定を行います。`config/aws.php`というファイルを作り、環境変数から読み取った値をセットします。

```php:config/aws.php
<?php

return [
    'cloudwatch' => [
        'log' => [
            'group_name' => env('AWS_CLOUDWATCH_LOG_GROUP_NAME'),
            'stream_name' => env('AWS_CLOUDWATCH_LOG_STREAM_NAME'),
            'retention' => env('AWS_CLOUDWATCH_LOG_RETENTION'),
        ]
    ],
    'default_region' => env('AWS_DEFAULT_REGION'),
];
```

そして、`config/logging.php`にCloudWatchへログを送信するためのチャネルを追加します。handlerには先ほどコンテナへ登録したハンドラのクラスを設定します。json形式でログを出力したい場合はformatterのコメントを解除してください。

```php:config/logging.php
....
        'emergency' => [
            'path' => storage_path('logs/laravel.log'),
        ],

        'cloudwatch' => [
            'driver' => 'monolog',
            'handler' => Maxbanton\Cwh\Handler\CloudWatch::class,
            // 'formatter' => Monolog\Formatter\JsonFormatter::class, json形式でログを送信する時はコメント解除する
        ],
    ],
];
```

最後に、.envファイルへ環境変数の登録をします。
LOG_CHANNELには先ほど`config/logging.php`に追加したログチャネルの名前を設定します。
あとは、AWSの認証情報とロググループ、ログストリーム、ログの保存期間などの設定値を入れます。**この時登録するIAM USERには対象のロググループとログストリームへのアクセス権が必要なので注意してください！**


```shell:.env
...
LOG_CHANNEL=cloudwatch
...
AWS_ACCESS_KEY_ID=XXXX
AWS_SECRET_ACCESS_KEY=XXXX
AWS_DEFAULT_REGION=ap-northeast-1
AWS_CLOUDWATCH_LOG_GROUP_NAME=laravel_cloudwatch_group
AWS_CLOUDWATCH_LOG_STREAM_NAME=laravel_cloudwatch_stream
AWS_CLOUDWATCH_LOG_RETENTION=1
```

# 実行

`routes/web.php`にログの出力を仕込んでアクセスします。

```php:routes/web.php
...
Route::get('/', function () {
    \Illuminate\Support\Facades\Log::info('ok');
    return view('welcome');
});
```

AWSコンソールからCloudWatchログを確認すると、ログが飛んできてるのがわかると思います。

![スクリーンショット 2021-03-04 21.25.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614264/6f6de8f3-989b-adda-d540-9d0b77c2c6e9.png)


# 最後に
今回の方法は、ログのフォーマット変換から送信まで全てPHPのレイヤーで行っています。お手軽な一方で、PHPの処理には負担がかかっていることが想定できます。大量のログを飛ばすような場合は、やはり一度標準出力に出してからCloudWatchエージェントなりaws firelensなりでCloudWatchへ飛ばすのが良いのかなと思います。
実際に計測したわけじゃないので、どれくらい負荷がかかっているのかは分かりませんが。。。