知りたかったこと
Amazon API GatewayとAWS Lambda とでサーバレスなAPIを構築し、デバッグに有用なログを得られるのかを知りたいんです。Lambda関数は変更せずに、マネージメントコンソールの設定変更で出力されるログは何ですか。
3行で
- AIMポリシー
AmazonAPIGatewayPushToCloudWatchLogs
を付与したIAMロールをAPIGatewayに設定 - APIGatewayのステージエディタの「ログ/トレース」タブで 「CloudWatch ログを有効化」にチェックをつけて、追加表示される「リクエスト/レスポンスをすべてログ」にもチェックをつける
- CloudWatch Logsロググループ「API-Gateway-Execution-Logs_{rest-api-id}/{stage-name}」を見る
構成
シンプルに API Gateway の Lambda プロキシ統合 を使って Lambda関数を呼び出します。
Lambda
デバッグに使えそうなログは無いので割愛します。
API Gateway
前提条件
AmazonAPIGatewayPushToCloudWatchLogs
が付与されたIAM ロールを作成し、
「設定」にある CloudWatch ログのロール ARN にAIM ロールのARNを記載しておきます。
設定項目
ステージエディターの「ログ/トレース」タブで設定を行います。
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バイトを超えると切り捨てられた からです。切り捨てられた部分もログに残すには、Lambda関数内で出力する必要があります。
カスタムアクセスのログ記録
自分が目的とする集計を行うために、書式をカスタマイズしたログを出力できます。Apacheの CustomLog が近いですね。これはデバッグとは違うので割愛します。
Lambdaでログを出したい
言語ごとのお作法はあるのだけど、Lmabda関数内で標準出力か標準エラー出力に出力します。
今回は Lambda (Python) からのログ出力Tipsを参考にして実装しました。
ログは CloudWatch Logsのロググループ /aws/lambda/{Lmabda関数名}
に出力されます。
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