SAM で API Gateway の CloudWatch Logs を有効にしたい

この記事は、AWS Serverless Application Model (AWS SAM) で API Gateway の CloudWatch Logs を有効化しようとした際の記録です。


# [AWS Serverless Application Model (AWS SAM)](https://github.com/awslabs/serverless-application-model) とは

- AWS がサーバーレスアプリケーションを構築するためのフレームワーク
- Lambda, API Gateway, DynamoDB などのリソースを手軽に管理できる
- ベースは AWS CloudFormation
- 参考：[AWS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する](https://dev.classmethod.jp/cloud/aws/aws-serverless-application-model/)


# API Gateway の「CloudWatch Logs の有効化」とは

有効にすることで、API Gateway のログを CloudWatch Logs の以下の場所に保存されるようになります。

> API-Gateway-Execution-Logs_<10桁のID>/<ステージ名> 

![apig-log.png](https://qiita-image-store.s3.amazonaws.com/0/93531/1af9cc0e-96e7-66e7-4235-25704e0181fa.png)

- 手順
 1. AWS Management Console を開く
 2. API Gateway を開く
 3. API を選択する
 4. ステージを選択する
 5. ログのタブを選択する

- 参考
  - [API Gatewayのログ出力を有効化する](https://qiita.com/nao-guitarist/items/f224adbcd2d55693d5b1)
  - [API Gateway コンソールを使用して API ログ作成をセットアップする](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-logging.html#set-up-access-logging-using-console)


# API Gateway の「CloudWatch Logs の有効化」の設定

## SAM の AWS::Serverless::Api の設定を確認してみた

- SAM の AWS::Serverless::Api ではログの設定ができなかった。。。
  - 参考：[AWS::Serverless::Api](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi)


## SAM の Issues を探してみた

- そのうち対応するみたい。。。
  - [Issue #248: Support API Gateway Logging Metrics, Usage Plans, EndpointConfiguration, and CORS](https://github.com/awslabs/serverless-application-model/issues/248)


## さらに、SAM の Issues を探してみた

- 3つの workaround が
  - [Issue #297: Support method settings for APIGateway stages](https://github.com/awslabs/serverless-application-model/issues/297)

#### 1. AWS Management Console で手動で設定する
- SAM の AWS::Serverless::Api でリソースを作成する度にログの設定が元に戻ってしまった。。。



#### 2. API の deploy 後に AWS CLI で設定を更新する
   - SAM の AWS::Serverless::Api でリソース作成後に、rest-api-id を取得できればできそう。
   - AWS CLI で rest-api-id を取得後、以下でログの設定に成功

```bash
aws apigateway update-stage \
  --rest-api-id xxx \
  --stage-name prod \
  --patch-operations \
      "op=replace,path=/*/*/logging/dataTrace,value=true" \
      "op=replace,path=/*/*/logging/loglevel,value=INFO"
```

#### 3. AWS CloudFormation の AWS::ApiGateway::Stage の MethodSettings で設定する
  - AWS::ApiGateway::Stage のリソース作成には RestApiId や DeploymentId が必要
  - AWS::Serverless::Api から RestApiId は取得できるが、DeploymentId が取得できない。。。
  - 参考
     - [AWS::ApiGateway::Stage](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html)
     - [Amazon API Gateway ステージの MethodSetting](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html)

#### 4. （おまけ）AWS CloudFormation の AWS::ApiGateway::Deployment の StageDescription で設定する
- AWS::Serverless::Api と　AWS::ApiGateway::Deployment のリソース作成仕様とするとエラーが・・・

> CREATE_FAILED	AWS::ApiGateway::Deployment	Deployment	StageDescription cannot be specified when stage referenced by StageName already exists

```yaml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  SampleApi: 
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionUri: swagger.yml

  SampleApiDeployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref SampleApi
      StageName: prod
      StageDescription: StageDescription
        - DataTraceEnabled: true
          LoggingLevel: INFO
          MetricsEnabled: true
```

  - 参考
    - [AWS::ApiGateway::Deployment](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html)
    - [Amazon API Gateway デプロイの StageDescription](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription.html#cfn-apigateway-deployment-stagedescription-methodsettings)
    - [Amazon API Gateway デプロイの MethodSetting](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-deployment-stagedescription-methodsetting.html)


# まとめ

以下の手順で、SAM の AWS::Serverless::Api のデプロイ後、AWS CLI で API Gateway の CloudWatch Logs の有効化ができた。

```bash:deploy.sh
aws cloudformation package \
  --template-file /path_to_template/template.yml \
  --output-template-file /path_to_template/template_output.yml \
  --s3-bucket my_deploy_bucket \
  --region aws_region

aws cloudformation deploy \
  --template-file /path_to_template/template_output.yml \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
  --parameter-overrides \
      Environment=prod \
  --stack-name my_stack_name \
  --region aws_region

aws cloudformation list-stack-resources \
  --stack-name my_stack_name \
  --region aws_region \
  | jq -r '.StackResourceSummaries[] | select(.ResourceType == "AWS::ApiGateway::RestApi") | .PhysicalResourceId' > rest-api-id.txt

while read api_id
do
  aws apigateway update-stage \
    --rest-api-id ${api_id} \
    --stage-name prod \
    --patch-operations \
      "op=replace,path=/*/*/logging/dataTrace,value=true" \
      "op=replace,path=/*/*/logging/loglevel,value=INFO" \
    --region aws_region
done < rest-api-id.txt
```

# SAM で API Gateway の CloudWatch Logs の設定ができるようになっていました（2018/05/13 追記）

2018/03/13 にリリースされた 1.4.0 から SAM で設定できるようになっていました。

- Release 1.4.0 : [Loads of API features, bug fixes and improvements](https://github.com/awslabs/serverless-application-model/releases/tag/1.4.0)
  - Logging, Metrics, CacheTTL: Any Stage Settings

## SAM の AWS::Serverless::Api で設定する

- 参考：[AWS::Serverless::Api](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi)

```yaml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  SampleApi: 
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionUri: path_to_swagger/swagger.yml
      MethodSettings:
        - DataTraceEnabled: true
          LoggingLevel: 'ERROR'
          ResourcePath: '/*'
          HttpMethod: '*'
```