APIGateway + AWS Lambda の構成でCloudWatch Logsに出力されるログ

## 知りたかったこと

Amazon API GatewayとAWS Lambda とでサーバレスなAPIを構築し、デバッグに有用なログを得られるのかを知りたいんです。Lambda関数は変更せずに、マネージメントコンソールの設定変更で出力されるログは何ですか。

## 3行で

1. AIMポリシー `AmazonAPIGatewayPushToCloudWatchLogs` を付与したIAMロールをAPIGatewayに設定
2. APIGatewayのステージエディタの「ログ/トレース」タブで 「CloudWatch ログを有効化」にチェックをつけて、追加表示される「リクエスト/レスポンスをすべてログ」にもチェックをつける
3. CloudWatch Logsロググループ「API-Gateway-Execution-Logs_{rest-api-id}/{stage-name}」を見る

## 構成

シンプルに　API Gateway の Lambda プロキシ統合 を使って　Lambda関数を呼び出します。

![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145742/9615d9ab-146f-3f1e-c5ae-3a99a3387e9d.png)

## Lambda

デバッグに使えそうなログは無いので割愛します。


## API Gateway

### 前提条件

[`AmazonAPIGatewayPushToCloudWatchLogs` が付与されたIAM ロール](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-logging.html#set-up-access-logging-permissions)を作成し、

![APIGatewayに付与するIAMロール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145742/7837acd2-c8a1-4576-edd2-b697cdb34045.png)

「設定」にある [*CloudWatch ログのロール ARN*](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-logging.html#set-up-access-logging-using-console) にAIM ロールのARNを記載しておきます。

![APIGatewayにAIMロールを記載.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145742/19db54c3-0fef-fa18-6dac-06b685824c31.png)

### 設定項目

ステージエディターの「ログ/トレース」タブで設定を行います。

![APIGateway.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145742/e61e1a18-8ebf-b633-cade-4ef47d66d67e.png)


### CloudWatchのログを有効化

ログレベルが「INFO」と「ERROR」の2段階あり、デバッグ目的なので「INFO」にしました。出力先の CloudWatch のロググループ名は `API-Gateway-Execution-Logs_{rest-api-id}/{stage-name}` です。


出力されたログの先頭にあるUUIDは API GatewayのリクエストIDなので、リクエスト単位にログを見る場合には、"(bc0d531d-0bb2-4d13-954e-a184e04fd024)"　を条件にしてログを検索します。

併せて LmabdaやX-Ray のログを見る時には `X-ray Tracing ID` の「1-5eb74b6d-b16741006c6e80a83474b828」を頼りに検索します。

```
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Extended Request Id: MSi5LEIuNjMFUng=
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Verifying Usage Plan for request: bc0d531d-0bb2-4d13-954e-a184e04fd024. API Key:  API Stage: {rest-api-id}/Prod
(bc0d531d-0bb2-4d13-954e-a184e04fd024) API Key  authorized because method 'POST /path/to/resource/bar' does not require API Key. Request will not contribute to throttle or quota limits
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Usage Plan check succeeded for API Key  and API Stage {rest-api-id}/Prod
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Starting execution for request: bc0d531d-0bb2-4d13-954e-a184e04fd024
(bc0d531d-0bb2-4d13-954e-a184e04fd024) HTTP Method: POST, Resource Path: /path/to/resource
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Successfully completed execution
(bc0d531d-0bb2-4d13-954e-a184e04fd024) Method completed with status: 200
(bc0d531d-0bb2-4d13-954e-a184e04fd024) AWS Integration Endpoint RequestId : aea58c25-5c59-48bb-9cd4-9a220e3f66b6
(bc0d531d-0bb2-4d13-954e-a184e04fd024) X-ray Tracing ID : Root=1-5eb74b6d-b16741006c6e80a83474b828
```

### CloudWatchのログを有効化 + リクエスト/レスポンスをすべてログ

まずは、上と同様に APIGatwayがクライアントから受け付けたログが出力されます。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Extended Request Id: MSlVjFtztjMFe2A=
(3324e0d1-f650-49ef-8795-48fd8b299146) Verifying Usage Plan for request: 3324e0d1-f650-49ef-8795-48fd8b299146. API Key:  API Stage: {rest-api-id}/Prod
(3324e0d1-f650-49ef-8795-48fd8b299146) API Key  authorized because method 'POST /path/to/resource/{foo}' does not require API Key. Request will not contribute to throttle or quota limits
(3324e0d1-f650-49ef-8795-48fd8b299146) Usage Plan check succeeded for API Key  and API Stage {rest-api-id}/Prod
(3324e0d1-f650-49ef-8795-48fd8b299146) Starting execution for request: 3324e0d1-f650-49ef-8795-48fd8b299146
(3324e0d1-f650-49ef-8795-48fd8b299146) HTTP Method: POST, Resource Path: /path/to/resource/bar
(3324e0d1-f650-49ef-8795-48fd8b299146) Method request path: {foo=bar}
```

そして、APIGatewayが受けたリクエストが追加されています。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Method request query string: {}
(3324e0d1-f650-49ef-8795-48fd8b299146) Method request headers: {Accept=application/vnd.git-lfs+json; charset=utf-8, CloudFront-Viewer-Country=JP, CloudFront-Forwarded-Proto=https, CloudFront-Is-Tablet-Viewer=false, CloudFront-Is-Mobile-Viewer=false, User-Agent=git-lfs/2.7.1 (GitHub; windows amd64; go 1.11.5; git 6b7fb6e3), X-Forwarded-Proto=https, CloudFront-Is-SmartTV-Viewer=false, Host={rest-api-id}.execute-api.ap-northeast-1.amazonaws.com, Accept-Encoding=gzip, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-5eb74f56-1a4129d0e8d9cd443d9cc9cc, Via=1.1 10885a2c24fad0ff660a4e3a8e3fb801.cloudfront.net (CloudFront), X-Amz-Cf-Id=2qTDIXcdtIbqzi5PmPyOtCAje3kLR08QP0mMzg3jhn0f_btYD5WVBA==, X-Forwarded-For=1.2.3.4, 52.46.61.80, CloudFront-Is-Desktop-Viewer=true, Content-Type=application/vnd.git-lfs+json; charset=utf-8}
(3324e0d1-f650-49ef-8795-48fd8b299146) Method request body before transformations: [リクエストbodyのjson]
```

次に、Lambda に転送するリクエストが追加されています。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Endpoint request URI: https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-function-name/invocations
(3324e0d1-f650-49ef-8795-48fd8b299146) Endpoint request headers: {x-amzn-lambda-integration-tag=3324e0d1-f650-49ef-8795-48fd8b299146, Authorization=*****************************************************************************************************************************************************************************************************************************************************************************************************************************6a03ab, X-Amz-Date=20200510T004822Z, x-amzn-apigateway-api-id={rest-api-id}, X-Amz-Source-Arn=arn:aws:execute-api:ap-northeast-1:123456789012:{rest-api-id}/Prod/POST/path/to/resource/{foo}, Accept=application/vnd.git-lfs+json, User-Agent=AmazonAPIGateway_{rest-api-id}, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEMD//////////wEaDmFwLW5vcnRoZWFzdC0xIkUwQwIgJCQ9yemryYhusjjqO5X7vfXP4L/ZkyEJvY2KcLF+OeICH0VMfSWvR68aem5q0WGT4PqK+HXiv/LY8UZaVYI+aZgqxwMI+v//////////ARABGgw5NjkyMzY4NTQ2MjYiDP/DCRaYwCfP4X9DRCqbA5PUEzYXWEzaIaEgGMBxMoUivJcHi5PD2Vu3pxI47I6UvAU+knfnmyrn3/18wpliOrTOVFEnIKkBniX/dLUkPnz4ReHPLR4MH [TRUNCATED]
(3324e0d1-f650-49ef-8795-48fd8b299146) Endpoint request body after transformations: {"resource":"/path/to/resource/{foo}","path":"/path/to/resource/bar","httpMethod":"POST","headers":{"Accept":"application/vnd.git-lfs+json; charset=utf-8","Accept-Encoding":"gzip","CloudFront-Forwarded-Proto":"https","CloudFront-Is-Desktop-Viewer":"true","CloudFront-Is-Mobile-Viewer":"false","CloudFront-Is-SmartTV-Viewer":"false","CloudFront-Is-Tablet-Viewer":"false","CloudFront-Viewer-Country":"JP","Content-Type":"application/vnd.git-lfs+json; charset=utf-8","Host":"{rest-api-id}.execute-api.ap-northeast-1.amazonaws.com","User-Agent":"git-lfs/2.7.1 (GitHub; windows amd64; go 1.11.5; git 6b7fb6e3)","Via":"1.1 10885a2c24fad0ff660a4e3a8e3fb801.cloudfront.net (CloudFront)","X-Amz-Cf-Id":"2qTDIXcdtIbqzi5PmPyOtCAje3kLR08QP0mMzg3jhn0f_btYD5WVBA==","X-Amzn-Trace-Id":"Root=1-5eb74f56-1a4129d0e8d9cd443d9cc9cc","X-Forwarded-For":"1.2.3.4, 52.46.61.80","X-Forwarded-Port":"443","X-Forwarded-Proto":"https"},"multiValueHeaders":{"Accept":["application/v [TRUNCATED]
(3324e0d1-f650-49ef-8795-48fd8b299146) Sending request to https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-function-name/invocations
```

その次に、AWS Lambda からのレスポンスが追加されています。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Received response. Status: 200, Integration latency: 416 ms
(3324e0d1-f650-49ef-8795-48fd8b299146) Endpoint response headers: {Date=Sun, 10 May 2020 00:48:23 GMT, Content-Type=application/json, Content-Length=3027, Connection=keep-alive, x-amzn-RequestId=253551a5-d8a9-4d7d-89a0-e2f4db387982, x-amzn-Remapped-Content-Length=0, X-Amz-Executed-Version=$LATEST, X-Amzn-Trace-Id=root=1-5eb74f56-1a4129d0e8d9cd443d9cc9cc;parent=0b2a5b85e09447ba;sampled=1}
(3324e0d1-f650-49ef-8795-48fd8b299146) Endpoint response body before transformations: {"statusCode": 200, "headers": {"content-type": "application/json"}, "body": "{\"transfer\": \"basic\", \"objects\": [{\"oid\": \"4d8decb90638873b9bd5cb412ddb68faf13969256b44ea8220b067eefd8be9da\", \"size\": 2176965, \"authenticated\": true, \"actions\": {\"upload\": {\"href\": \"https://my-bucket.s3.amazonaws.com/4d8decb90638873b9bd5cb412ddb68faf13969256b44ea8220b067eefd8be9da?AWSAccessKeyId=ASIATESO3DVI7MMRDSJB&Signature=bvM6ICJmmfZ1PA6LupcuvLKTMiM%3D&content-type=application%2Foctet-stream&x-amz-security-token=IQoJb3JpZ2luX2VjEMH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLW5vcnRoZWFzdC0xIkcwRQIhAJbedKFrvqz1mogRl%2BQF51DR1dkYHE1dFOoKmxx%2BVssFAiB0rRVENA%2BfbYZoc9biCMWdxsqjD0eQIRRLPdNQNEUmYSrdAQj6%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDIxNTk4NzQ2MTQ1NyIMb0Jr%2FC0G3kfyiZ%2BBKrEB9Qb7D8g59lrWErz1ePVyd8%2F%2B2mEcOUAYPONu3pcWQAnwcNDsrQdwXGqGiLhjnqDXPt8WZgvogxxk9J264%2FC4tgVeIJk0F3mVUmsQGQXO%2F9akgfTnP%2FuFURZ6PxaoJyBA1B5XwC%2FrhsdmGWeebJp3mlfZHlksJ3n1RVCF63ptrBAq1a [TRUNCATED]
```

最後に、APIGatewayがクライアントに返すレスポンスが追加されています。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Method response body after transformations: {"transfer": "basic", "objects": [{"oid": "4d8decb90638873b9bd5cb412ddb68faf13969256b44ea8220b067eefd8be9da", "size": 2176965, "authenticated": true, "actions": {"upload": {"href": "https://my-bucket.s3.amazonaws.com/4d8decb90638873b9bd5cb412ddb68faf13969256b44ea8220b067eefd8be9da?AWSAccessKeyId=ASIATESO3DVI7MMRDSJB&Signature=bvM6ICJmmfZ1PA6LupcuvLKTMiM%3D&content-type=application%2Foctet-stream&x-amz-security-token=IQoJb3JpZ2luX2VjEMH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLW5vcnRoZWFzdC0xIkcwRQIhAJbedKFrvqz1mogRl%2BQF51DR1dkYHE1dFOoKmxx%2BVssFAiB0rRVENA%2BfbYZoc9biCMWdxsqjD0eQIRRLPdNQNEUmYSrdAQj6%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDIxNTk4NzQ2MTQ1NyIMb0Jr%2FC0G3kfyiZ%2BBKrEB9Qb7D8g59lrWErz1ePVyd8%2F%2B2mEcOUAYPONu3pcWQAnwcNDsrQdwXGqGiLhjnqDXPt8WZgvogxxk9J264%2FC4tgVeIJk0F3mVUmsQGQXO%2F9akgfTnP%2FuFURZ6PxaoJyBA1B5XwC%2FrhsdmGWeebJp3mlfZHlksJ3n1RVCF63ptrBAq1abuVRYtLBE9sg01r%2BsR2aL48mUmzEPQRMhedsx%2BKUDzVjZGZ2lAIYjmRfkrZCyyML6c3fUFOuABJ2XCem0aIz0hlAa%2BPMc6Q8 [TRUNCATED]
(3324e0d1-f650-49ef-8795-48fd8b299146) Method response headers: {content-type=application/json, X-Amzn-Trace-Id=Root=1-5eb74f56-1a4129d0e8d9cd443d9cc9cc}
```

残りは上と同じです。

```
(3324e0d1-f650-49ef-8795-48fd8b299146) Successfully completed execution
(3324e0d1-f650-49ef-8795-48fd8b299146) Method completed with status: 200
(3324e0d1-f650-49ef-8795-48fd8b299146) AWS Integration Endpoint RequestId : 253551a5-d8a9-4d7d-89a0-e2f4db387982
(3324e0d1-f650-49ef-8795-48fd8b299146) X-ray Tracing ID : Root=1-5eb74f56-1a4129d0e8d9cd443d9cc9cc
```



出力されたログをみると 末尾に `[TRUNCATED]` あるのは [API Gatewayの仕様で 1024バイトを超えると切り捨てられた](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-known-issues.html) からです。切り捨てられた部分もログに残すには、Lambda関数内で出力する必要があります。


### カスタムアクセスのログ記録


自分が目的とする集計を行うために、書式をカスタマイズしたログを出力できます。Apacheの CustomLog が近いですね。これはデバッグとは違うので割愛します。

## Lambdaでログを出したい

言語ごとのお作法はあるのだけど、Lmabda関数内で標準出力か標準エラー出力に出力します。

- [Node.jsの場合](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-logging.html) 
- [Pythonの場合](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-logging.html)
- [Javaの場合](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-logging.html)

今回は [Lambda (Python) からのログ出力Tips](https://qiita.com/ohtsuka1317/items/6d4f527e6a0ccf0903f7)を参考にして実装しました。

ログは CloudWatch Logsのロググループ `/aws/lambda/{Lmabda関数名}` に出力されます。

```python:lambda_handler.py
import json
import logging
import os


def lambda_handler(event, context):
    logger = logging.getLogger(__name__)

    level_name = os.environ.get('LOG_LEVEL')
    level = logging.getLevelName(level_name)
    if not isinstance(level, int):
        level = logging.INFO
    logger.setLevel(level)

    response = {}

    try:
        response = main処理(event, context)
    except Exception as ex:
        response = {
            "statusCode": "500",
            "message": ex.args[0]
        }
    finally:
        logger.info(json.dumps({
            "request": {
                "event": event
            },
            "response": response
        }))

        return response

```