Skip to content

API Gateway Logging

Level: Error

Initial version: 0.1.3

cfn-lint: ES2000

tflint (REST): aws_apigateway_stage_logging_rule

tflint (HTTP): aws_apigatewayv2_stage_logging_rule

Amazon API Gateway can send logs to Amazon CloudWatch Logs and Amazon Kinesis Data Firehose for centralization.

AWS SAM templates

For AWS::Serverless::Api and AWS::Serverless::HttpApi resources, this rule checks the effective stage settings directly, including values inherited from Globals.Api or Globals.HttpApi. Function Api events without RestApiId and HttpApi events without ApiId create implicit APIs, which are also checked using the applicable global settings; each implicit API finding is reported once per owning function, so functions sharing an implicit API can each receive a finding. REST APIs use AccessLogSetting and HTTP APIs use AccessLogSettings; a missing DestinationArn fails the check.

Implementations for REST APIs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { LogGroup } from '@aws-cdk/aws-logs';
import { LogGroupLogDestination, RestApi } from '@aws-cdk/aws-apigateway';

export class MyStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const myLogGroup = new LogGroup(
      scope, 'MyLogGroup'
    );

    const myApi = new RestApi(
      scope, 'MyApi',
      {
        deployOptions: {
          // Setup logging for API Gateway
          accessLogDestination: new LogGroupLogDestination(myLogGroup),
          accessLogFormat: JSON.stringify({
            "stage" : "$context.stage",
            "request_id" : "$context.requestId",
            "api_id" : "$context.apiId",
            "resource_path" : "$context.resourcePath",
            "resource_id" : "$context.resourceId",
            "http_method" : "$context.httpMethod",
            "source_ip" : "$context.identity.sourceIp",
            "user-agent" : "$context.identity.userAgent",
            "account_id" : "$context.identity.accountId",
            "api_key" : "$context.identity.apiKey",
            "caller" : "$context.identity.caller",
            "user" : "$context.identity.user",
            "user_arn" : "$context.identity.userArn",
            "integration_latency": $context.integration.latency
          }),
        }
      }
    );
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "Resource": {
    "Type": "AWS::Serverless::Api",
    "Properties": {
      "DefinitionUri": "openapi.yaml",
      "StageName": "prod",

      // Setup logging for API Gateway
      "AccessLogSetting":{
        "DestinationArn": "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group",
        "Format": "{ \"stage\" : \"$context.stage\", \"request_id\" : \"$context.requestId\", \"api_id\" : \"$context.apiId\", \"resource_path\" : \"$context.resourcePath\", \"resource_id\" : \"$context.resourceId\", \"http_method\" : \"$context.httpMethod\", \"source_ip\" : \"$context.identity.sourceIp\", \"user-agent\" : \"$context.identity.userAgent\", \"account_id\" : \"$context.identity.accountId\", \"api_key\" : \"$context.identity.apiKey\", \"caller\" : \"$context.identity.caller\", \"user\" : \"$context.identity.user\", \"user_arn\" : \"$context.identity.userArn\", \"integration_latency\": $context.integration.latency }"
      }
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Resources:
  Api:
    Type: AWS::Serverless::Api
    Properties:
      DefinitionUri: openapi.yaml
      StageName: prod

      # Setup logging for API Gateway
      AccessLogSetting:
        DestinationArn: "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group"
        Format: |
          {
            "stage" : "$context.stage",
            "request_id" : "$context.requestId",
            "api_id" : "$context.apiId",
            "resource_path" : "$context.resourcePath",
            "resource_id" : "$context.resourceId",
            "http_method" : "$context.httpMethod",
            "source_ip" : "$context.identity.sourceIp",
            "user-agent" : "$context.identity.userAgent",
            "account_id" : "$context.identity.accountId",
            "api_key" : "$context.identity.apiKey",
            "caller" : "$context.identity.caller",
            "user" : "$context.identity.user",
            "user_arn" : "$context.identity.userArn",
            "integration_latency": $context.integration.latency
          }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
provider:
  name: aws
  logs:
    # Setup logging for API Gateway
    restApi:
      accessLogging: true
      format: |
        {
          "stage" : "$context.stage",
          "request_id" : "$context.requestId",
          "api_id" : "$context.apiId",
          "resource_path" : "$context.resourcePath",
          "resource_id" : "$context.resourceId",
          "http_method" : "$context.httpMethod",
          "source_ip" : "$context.identity.sourceIp",
          "user-agent" : "$context.identity.userAgent",
          "account_id" : "$context.identity.accountId",
          "api_key" : "$context.identity.apiKey",
          "caller" : "$context.identity.caller",
          "user" : "$context.identity.user",
          "user_arn" : "$context.identity.userArn",
          "integration_latency": $context.integration.latency
        }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
resource "aws_api_gateway_rest_api" "this" {
  body = file("openapi.yaml")
}

resource "aws_api_gateway_deployment" "this" {
  rest_api_id = aws_api_gateway_rest_api.this.id

  triggers = {
    redeployment = sha1(jsonencode(aws_api_gateway_rest_api.this.body))
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_stage" "this" {
  deployment_id = aws_api_gateway_deployment.this.id
  rest_api_id   = aws_api_gateway_rest_api.this.id
  stage_name    = "prod"

  # Setup logging for API Gateway
  access_log_settings {
    destination_arn = "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group"
    format = <<EOF
{
  "stage" : "$context.stage",
  "request_id" : "$context.requestId",
  "api_id" : "$context.apiId",
  "resource_path" : "$context.resourcePath",
  "resource_id" : "$context.resourceId",
  "http_method" : "$context.httpMethod",
  "source_ip" : "$context.identity.sourceIp",
  "user-agent" : "$context.identity.userAgent",
  "account_id" : "$context.identity.accountId",
  "api_key" : "$context.identity.apiKey",
  "caller" : "$context.identity.caller",
  "user" : "$context.identity.user",
  "user_arn" : "$context.identity.userArn",
  "integration_latency": $context.integration.latency
}
EOF
  }
}

Implementations for HTTP APIs

Remark: this is currently not supported in AWS CDK as an L2 construct at the moment. See this GitHub issue for more details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { CfnStage, HttpApi } from '@aws-cdk/aws-apigatewayv2';

export class MyStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const myApi = new HttpApi(
      scope, 'MyApi'
    );

    // Setup logging for API Gateway using escape hatch.

    // See https://github.com/aws/aws-cdk/issues/11100 and
    // https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_resource
    // for more information.
    const defaultStage = myApi.defaultStage.node.defaultChild as CfnStage;
    defaultStage.accessLogSettings = {
      destinationArn: '',
      format: JSON.stringify({
        "stage" : "$context.stage",
        "request_id" : "$context.requestId",
        "api_id" : "$context.apiId",
        "resource_path" : "$context.resourcePath",
        "resource_id" : "$context.resourceId",
        "http_method" : "$context.httpMethod",
        "source_ip" : "$context.identity.sourceIp",
        "user-agent" : "$context.identity.userAgent",
        "account_id" : "$context.identity.accountId",
        "api_key" : "$context.identity.apiKey",
        "caller" : "$context.identity.caller",
        "user" : "$context.identity.user",
        "user_arn" : "$context.identity.userArn",
        "integration_latency": $context.integration.latency
      }),
    };
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "Resource": {
    "Type": "AWS::Serverless::HttpApi",
    "Properties": {
      "DefinitionUri": "openapi.yaml",
      "StageName": "prod",

      // Setup logging for API Gateway
      "AccessLogSettings":{
        "DestinationArn": "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group",
        "Format": "{ \"stage\" : \"$context.stage\", \"request_id\" : \"$context.requestId\", \"api_id\" : \"$context.apiId\", \"resource_path\" : \"$context.resourcePath\", \"resource_id\" : \"$context.resourceId\", \"http_method\" : \"$context.httpMethod\", \"source_ip\" : \"$context.identity.sourceIp\", \"user-agent\" : \"$context.identity.userAgent\", \"account_id\" : \"$context.identity.accountId\", \"api_key\" : \"$context.identity.apiKey\", \"caller\" : \"$context.identity.caller\", \"user\" : \"$context.identity.user\", \"user_arn\" : \"$context.identity.userArn\", \"integration_latency\": $context.integration.latency }"
      }
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Resources:
  Api:
    Type: AWS::Serverless::HttpApi
    Properties:
      DefinitionUri: openapi.yaml
      StageName: prod

      # Setup logging for API Gateway
      AccessLogSettings:
        DestinationArn: "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group"
        Format: |
          {
            "stage" : "$context.stage",
            "request_id" : "$context.requestId",
            "api_id" : "$context.apiId",
            "resource_path" : "$context.resourcePath",
            "resource_id" : "$context.resourceId",
            "http_method" : "$context.httpMethod",
            "source_ip" : "$context.identity.sourceIp",
            "user-agent" : "$context.identity.userAgent",
            "account_id" : "$context.identity.accountId",
            "api_key" : "$context.identity.apiKey",
            "caller" : "$context.identity.caller",
            "user" : "$context.identity.user",
            "user_arn" : "$context.identity.userArn",
            "integration_latency": $context.integration.latency
          }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
provider:
  name: aws
  logs:
    httpApi:
      format: |
        {
          "stage" : "$context.stage",
          "request_id" : "$context.requestId",
          "api_id" : "$context.apiId",
          "resource_path" : "$context.resourcePath",
          "resource_id" : "$context.resourceId",
          "http_method" : "$context.httpMethod",
          "source_ip" : "$context.identity.sourceIp",
          "user-agent" : "$context.identity.userAgent",
          "account_id" : "$context.identity.accountId",
          "api_key" : "$context.identity.apiKey",
          "caller" : "$context.identity.caller",
          "user" : "$context.identity.user",
          "user_arn" : "$context.identity.userArn",
          "integration_latency": $context.integration.latency
        }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
resource "aws_apigatewayv2_api" "this" {
  name          = "my-api"
  protocol_type = "HTTP"
  body          = file("openapi.yaml") 
}

resource "aws_apigatewayv2_stage" "this" {
  api_id = aws_apigatewayv2_api.this.id
  name   = "prod"

  # Setup logging for API Gateway
  access_log_settings {
    destination_arn = "arn:aws:logs:eu-west-1:123456789012:log-group:my-log-group"
    format = <<EOF
{
  "stage" : "$context.stage",
  "request_id" : "$context.requestId",
  "api_id" : "$context.apiId",
  "resource_path" : "$context.resourcePath",
  "resource_id" : "$context.resourceId",
  "http_method" : "$context.httpMethod",
  "source_ip" : "$context.identity.sourceIp",
  "user-agent" : "$context.identity.userAgent",
  "account_id" : "$context.identity.accountId",
  "api_key" : "$context.identity.apiKey",
  "caller" : "$context.identity.caller",
  "user" : "$context.identity.user",
  "user_arn" : "$context.identity.userArn",
  "integration_latency": $context.integration.latency
}
EOF
  }
}

See also