SymfonyでCloudWatchへログ送信

# はじめに
こちらの記事のSymfony版です。やってることはほとんど同じで、主に違うのはコンテナへのサービスの登録方法などです。

https://qiita.com/ggg-mzkr/items/0201b411425a75deca52

# 方法

ライブラリをインストールします。

```
 composer require maxbanton/cwh
```

ハンドラーと`CloudWatchLogsClient`をコンテナへ登録します。コメントアウトしてある`$client: '@Aws\CloudWatchLogs\CloudWatchLogsClient'`はあってもなくても大丈夫です。`Aws\CloudWatchLogs\CloudWatchLogsClient`のサービスIDをクラス名と同じにすることで、そのClassは自動的にDIされるようになるというカラクリです。便利ですね。

さて、実はここで一つ問題が発生しました。、`Aws\CloudWatchLogs\CloudWatchLogsClient`に渡しているcredentials情報なのですが、本来であれば`Aws\Credentials\CredentialProvider::defaultProvider()`を使いたいところなのです。が、認証情報にこれを渡すと`The security token included in the request is expired`というエラーが発生してしまいました。原因は分からなかったので、一旦key/secretで認証します。

```yaml:config/services.yaml
parameters:
    aws.region: '%env(resolve:AWS_DEFAULT_REGION)%'
    aws.key: '%env(resolve:AWS_ACCESS_KEY_ID)%'
    aws.secret: '%env(resolve:AWS_SECRET_ACCESS_KEY)%'
    aws.cloudwatch.log_group_name: '%env(resolve:AWS_CLOUDWATCH_LOG_GROUP_NAME)%'
    aws.cloudwatch.log_stream_name: '%env(resolve:AWS_CLOUDWATCH_LOG_STREAM_NAME)%'

services:
   ...
    Aws\CloudWatchLogs\CloudWatchLogsClient:
        class: Aws\CloudWatchLogs\CloudWatchLogsClient
        arguments:
            - region: '%aws.region%'
              version: 'latest'
              credentials: { key: '%aws.key%', secret: '%aws.secret%' }

    Maxbanton\Cwh\Handler\CloudWatch:
        class: Maxbanton\Cwh\Handler\CloudWatch
        arguments:
            # $client: '@Aws\CloudWatchLogs\CloudWatchLogsClient'
            $group: '%aws.cloudwatch.log_group_name%'
            $stream: '%aws.cloudwatch.log_stream_name%'

```

先ほどコンテナへ登録したハンドラを使うように設定します。json形式で送信するときはコメントアウトされている箇所を解除してください。

```yaml:config/packages/dev/monolog.yaml
monolog:
    handlers:
        main:
            type: stream
...
        #format_json:
        #    handler: cloudwatch
        #    type: filter
        #    formatter: 'monolog.formatter.json'
        #    channels: ["app"]
        cloudwatch:
            type: service
            id: Maxbanton\Cwh\Handler\CloudWatch
            channels: ["app"]
```


そして最後に環境変数に、諸々の値をセットします。**登録するIAM USERには対象のロググループとログストリームへのアクセス権が必要なので注意してください！**

```env:.env
.....
AWS_ACCESS_KEY_ID=XXXXX
AWS_SECRET_ACCESS_KEY=XXXXX
AWS_DEFAULT_REGION=ap-northeast-1
AWS_CLOUDWATCH_LOG_GROUP_NAME=symfony_cloudwatch_group
AWS_CLOUDWATCH_LOG_STREAM_NAME=symfony_cloudwatch_stream
```


コントローラーにログ出力を仕込んでアクセスすると、CloudWatchへログが飛んでいます。

```php:src/Controller/HomeController.php
<?php

namespace App\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/home", name="home")
     * @param LoggerInterface $logger
     * @return Response
     */
    public function index(LoggerInterface $logger): Response
    {
        $logger->info('ok');
        return $this->json([]);
    }
}
```

![スクリーンショット 2021-03-05 22.04.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614264/96fd19a5-43cd-0f04-d382-554db32cc409.png)


# 最後に
Laravelはコンテナへのサービス登録にPHPを使うので、PHPさえ書ければなんとかなりますが、Symfonyはyaml（またはxml）で管理するのでSymfonyのことをわかっていないと書けないのが辛いところですね。学習コストが高いと言われる由縁だと思います。とは言え、慣れてしまえばさほど気にならなず、極めればPHPで書くよりも簡潔に記述できることも多いと思います。

そして`Aws\Credentials\CredentialProvider::defaultProvider()`を使うと認証エラーが発生したのが謎です。環境変数からうまくkey/secretを読み取れてないような気がしているのですが、AWSのSDKが複雑でデバッグに力尽きました。Laravelではエラーが発生しなかったので、SymfonyのDIの仕組みに問題がある（相性が悪い）のかもしれません。原因が分かり次第追記します。