メインコンテンツにスキップ

SAMでAPI GatewayのアクセスログをJSON形式で出力するぜ

はじめに

AWS SAM (Serverless Application Model) を使ってAPI Gatewayを管理する際、アクセスログを分析しやすいJSON形式で出力したいと考えるのは自然なことです。しかし、template.yaml でログフォーマットを設定しようとすると、次のようなエラーに直面することがあります。

Access Log format must be single line, new line character is allowed only at end of the format

このエラーの原因と、SAMテンプレート (template.yaml) を使ってAPI Gatewayのアクセスログを正しくJSON形式で設定する方法を解説します。

エラーの原因

このエラーは、API Gatewayがアクセスログのフォーマット定義として改行を含まない単一の文字列を要求するために発生します。

YAMLの複数行文字列(|>)を使って可読性のためにJSONを整形して記述すると、その改行がそのまま値に含まれてしまい、API Gatewayの要件に違反してしまうのです。

誤った例 (YAMLの複数行文字列):

MyApi:
  Type: AWS::Serverless::Api
  Properties:
    StageName: Prod
    AccessLogSetting:
      DestinationArn: !GetAtt MyApiLogGroup.Arn
      # この書き方では改行が含まれてしまいエラーになる
      Format: |
        {
          "requestId": "$context.requestId",
          "requestTime": "$context.requestTime",
          "httpMethod": "$context.httpMethod"
        }

正しい設定方法

解決策は、Format プロパティに改行を含まない1行のJSON文字列を渡すことです。

template.yamlでの解決策

template.yaml でこの問題を解決するには、YAMLの複数行機能を使わず、シングルクォート(')で囲んだ1行の文字列としてJSONを定義します。

横に長いので、スクロールして参照してください。

正しい例 (template.yaml):

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An example of API Gateway with JSON access logging.

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      # アクセスログの設定
      AccessLogSetting:
        # ログの出力先となるCloudWatch LogsのARNを指定
        DestinationArn: !GetAtt MyApiLogGroup.Arn
        # 改行を含まない1行のJSON文字列を指定する
        Format: '{"requestId":"$context.requestId","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath","status":"$context.status","sourceIp":"$context.identity.sourceIp"}'

  # ロググループの定義 (保存期間も設定しておく)
  MyApiLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 14

  # APIに紐づくLambda関数
  MyLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.handler
      Runtime: nodejs18.x
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref MyApi

このように、Format プロパティに1行の文字列を直接記述することで、API Gatewayの要件を満たし、エラーを回避できます。

設定するJSONフォーマットの例

以下は、一般的に利用されるコンテキスト変数をまとめたJSONフォーマットのサンプルです。このままコピーして Format に設定できます。(自分用)

'{"requestId":"$context.requestId","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath","status":"$context.status","responseLength":"$context.responseLength","responseTime":"$context.responseLatency","protocol":"$context.protocol","sourceIp":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent","stage":"$context.stage","domainName":"$context.domainName","accountId":"$context.accountId","apiId":"$context.apiId","errorMessage":"$context.error.message","errorMessageString":"$context.error.messageString","integrationStatus":"$context.integration.status","integrationLatency":"$context.integration.latency"}'

実際のログ出力

正しく設定すると、CloudWatch Logsには以下のような1行のJSONログが出力されるようになります。これにより、CloudWatch Logs Insightsなどを使ったログのクエリや分析が格段に容易になります。

{
    "requestId": "303a8495-3e25-4700-8263-1d7a8bb2f380",
    "requestTime": "27/Jul/2025:02:01:29 +0000",
    "httpMethod": "GET",
    "resourcePath": "/hello",
    "status": "200",
    "sourceIp": "133.203.185.64",
    ...
}