サブスクリプションフィルターをGUI以外で設定する場合の注意事項

# 概要
AWSのCloudWatch Logsにはサブスクリプションフィルターという機能があり、Kinesis Data Stream, Lambda, Amazon Data Firehose, OpenSearchにデータ連携を行うことが出来ます。  
これらの中で**Lambdaと連携するサブスクリプションフィルターを作成する場合**、CloudWatch Logsに対してLambda関数を実行する許可を付与する必要があります。
GUIでサブスクリプションフィルターを作成する場合、この設定は裏側で自動で設定されるためあまり意識する必要はないのですが、CLIで設定する場合は明示的に設定を行う必要があるため、注意が必要です。

# ユースケース
- GUIでLambdaサブスクリプションフィルターを作成する
- GUI以外(AWS CLI,CloudFormation, SDK)でLambdaサブスクリプションフィルターを作成する

# 設定手順
 ## GUIでの設定
GUIで設定する場合、ロググループの詳細画面から作成▲ → "Lambdaサブスクリプションフィルターを作成"を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662709/fb4f688a-ac98-4923-bf7c-8a7a58c137e7.png)

以下のような画面に遷移しますので、それぞれ値を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662709/05dadb74-7de5-be34-f1f0-f02017e60429.png)

| 項目  | 入力値 |
| - | - |
|Lambda関数 | このアカウント・リージョンに存在するLambda関数がプルダウン形式で表示されるので、送信先として指定したいLambda関数を選択します。 |
|ログの形式 | JSON・スペース区切り・VPCフローログなどから選択すると、サブスクリプションフィルターのパターンの項目に推奨値が入力されます。既にフィルターを行っているパターンが定まっているのであれば、初期値("その他")のまま次に進んで問題ありません。 |
|サブスクリプションフィルターのパターン | フィルターを行うパターンを指定します。 |
|サブスクリプションフィルター名 | フィルター名として任意の文字列を入力します。 |

全ての項目を入力後、”ストリーミングを開始”を選択すると、Lambdaとの連携が開始されます。  
フィルターパターンに不安がある場合は作成する前にパターンのテストを行うことも出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662709/0798626c-ee7e-9d62-bb6d-0339a08868b7.png)


なお、フィルターパターンは一致するパターンだけでなく一致しないパターン(not equal)等も指定出来ます。詳細は以下のドキュメントを確認してください。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#matching-terms-regex

"ストリーミングを開始"すると、連携先として指定されたLambdaは自動的に`log.region.amazonaws.com`からの実行が許可されるようになります。(以下のリソースベースのポリシーステートメント)
この設定がGUI以外の操作では行われないため、知らないとハマってしまう落とし穴になります。
![スクリーンショット 2024-05-07 213338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662709/727182d1-e990-db75-a744-ecaba4a42d29.png)
リソースベースのポリシーステートメントはLambdaの"設定>アクセス権限"から確認できます。


## AWS CLIでの設定
AWS CLIで設定を行う場合は、まず以下のコマンドでCloud Watch Logsに対象のLambdaを実行する権限を付与してください。
```
aws lambda add-permission --function-name "①<連携したいLambda名>" --statement-id "②<ステートメント名>" --principal "logs.ap-northeast-1.amazonaws.com" --action "lambda:InvokeFunction" --source-arn "③arn:aws:logs:ap-northeast-1:<Accountid>:log-group:<サブスクリプションフィルターを設定するLog Group>:*"
```
| # | 値 | 内容 | 
| --- | --- | --- |
| ① | 連携したいLambda名 | サブスクリプションフィルターの連携先として設定したいLambdaの名称(not Arn)を入力します。このLambdaを実行する権限を"log.ap-northeast-1.amazonaws.com"に与える、というコマンドです。 |
| ② | ステートメント名 | こちらは任意の文字列で問題ありません。Lambdaの権限設定の画面にステートメントIDとして表示されます。 | 
| ③ | ロググループのARN | サブスクリプションフィルターを設定したいロググループのArn(not 名称)を入力してください。 |

上記の権限付与コマンドを実行後、以下のようなサブスクリプションフィルターを設定するコマンドを実行します。
```
aws logs put-subscription-filter --log-group-name "①<ロググループ名>" --filter-name "②<フィルター名>" --filter-pattern "③<フィルターパターン>" --destination-arn "④<LambdaのArn>"
```
| # | 値 | 内容 | 
| --- | --- | --- |
| ① | ロググループ名 | サブスクリプションフィルターを設定するロググループを入力します。ここで入力するのはArnではなくロググループ名です。 |
| ② | フィルター名 | サブスクリプションフィルターの名称です。こちらは任意の文字列で問題ありません。  | 
| ③ | フィルターパターン | フィルターする文字列を設定します。フィルターを設定しない場合は"(空白)"を指定してください。何も入れない("")とエラーになります。 |
| ④ | LambdaのArn | 連携したいLambdaのArn(not 名称)を入力します。 |


権限付与コマンドを実行しないまま、先に`put-subscription-filter`のコマンドを実行すると以下のようなエラーになります。  
> Resource handler returned message: "Could not execute the lambda function. Make sure you have given CloudWatch Logs permission to execute your function."

## CloudFormationでの設定
CloudFormationで設定する場合は以下の通りです。  
各項目の入力値についてはAWS CLIと同様です。
```
Resources:
  LambdaPermisson:
      Type: AWS::Lambda::Permission
      Properties: 
        Action: lambda:InvokeFunction
        FunctionName: <連携したいLambda名>
        Principal: !Sub logs.${AWS::Region}.amazonaws.com
        SourceAccount: !Ref AWS::AccountId
        SourceArn: <ロググループのArn>
...
(中略)
...
  SubscriptionFilter:
      Type: AWS::Logs::SubscriptionFilter
      Properties:
        LogGroupName: 
        FilterPattern: <Filter名>
        DestinationArn:  <LambdaのArn>
```
面白いことに、AWS CLIとCloudFormationでは必須パラメータが異なります。  
例えば、Subscription Filterを設定する際、AWS CLIでの必須パラメータは`log-group-name`・`filter-name`・`filter-pattern`・`destination-arn`の4つですが、CloudFormationで設定する場合は`filter-name`は必須ではありません。
では、CloudFormationで設定する際の`filter-name`はどうなるのかと言うと、`<CFn Stack名>-<CFn Resource名>-<ランダム文字列>`が入ります。
＊awscliについてはリファレンスで実線のパラメータが必須、CloudFormationはRequired: Yesとなっているパラメータが必須

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/put-subscription-filter.html


https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-subscriptionfilter.html

## SDKでの設定
詳細なコードについては割愛しますが、Boto3を利用する場合もAWS CLI, CloudFormationと同様に権限付与を行ったうえでサブスクリプションフィルターを設定する必要があります。
```
response = client.add_permission(
    FunctionName='string',
    StatementId='string',
    Action='string',
    Principal='string'
    )
```
Lambda関数の指定については複数のフォーマットを利用でき、AWS CLIやCloudFormationより柔軟です。
```
Name formats
Function name – my-function (name-only), my-function:v1 (with alias).
Function ARN – arn:aws:lambda:us-west-2:123456789012:function:my-function.
Partial ARN – 123456789012:function:my-function.
You can append a version number or alias to any of the formats. The length constraint applies only to the full ARN. If you specify only the function name, it is limited to 64 characters in length.
```

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/add_permission.html

```
response = client.put_subscription_filter(
    logGroupName='string',
    filterName='string',
    filterPattern='string',
    destinationArn='string'
    )
```

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs/client/put_subscription_filter.html


# まとめ
本記事ではGUIおよびAWS CLI, CloudFormation, SDKを用いてCloudWatch Logsのサブスクリプションフィルターを設定する方法について紹介しました。  
一度に複数のロググループに設定する場合などCLI等を利用する場面は多いかと思いますが、その際にはロググループへのLambda関数実行権限の付与を忘れないようにしましょう。（これ以外にもGUIで自動設定されていて気づかなかったが、CLIでは手動で対応する必要があった、という設定にはたまに遭遇します。)
また、GUIで設定する場合はログ形式を指定することでフィルターパターンを推奨してくれたり、パターンのテストを行うことも出来ますので、GUI・CLIを上手く使い分けられると良いかと思います。