API Gateway Now Supports CloudWatch Logs Delivery: Trying Out Delivery to S3 and CloudWatch Logs

API Gateway Now Supports CloudWatch Logs Delivery: Trying Out Delivery to S3 and CloudWatch Logs

API Gateway REST API execution logs are now a supported CloudWatch Logs delivery source. Logs previously truncated beyond 1 KB can now reach 1 MB, with delivery to CloudWatch Logs, S3 or Firehose in JSON or Parquet.
2026.09.13

This page has been translated by machine translation. View original

Introduction

On September 10, 2026, it became possible to deliver API Gateway REST API execution logs via CloudWatch Logs delivery.

https://aws.amazon.com/about-aws/whats-new/2026/09/amazon-api-gateway-1-mb-execution-logs/

You can specify your own CloudWatch Logs log group, S3 bucket, or Firehose stream as the delivery destination. Standard execution logs truncate events at 1KB, but delivery can record events of up to 1MB. Payloads larger than 1MB are still truncated.

https://aws.amazon.com/blogs/compute/customize-amazon-api-gateway-destinations-for-execution-logs/

I configured both the conventional execution logs and this new delivery, and compared how the logs for the same request look.

What I Tested

I created a minimal REST API and Lambda with CloudFormation, and added settings to deliver execution logs to both CloudWatch Logs and S3. I ran the same request before and after adding the delivery, and checked the auto-managed log group and the logs at each delivery destination.

Test Environment

CloudFormation property constraints and differences in the values the CLI accepts affect the results, so here is the environment used this time.

Item Value
Region ap-northeast-1
AWS CLI 2.36.44
Lambda runtime python3.13
REST API /echo (ANY, Lambda proxy integration, stage prod)
Stage settings LoggingLevel: INFO / DataTraceEnabled: true

Creating the Minimal Configuration

To output execution logs, you must set cloudWatchRoleArn for each account and region. In the test account this time, I reused an already configured role. If it is not set, attach AmazonAPIGatewayPushToCloudWatchLogs to a role that trusts apigateway.amazonaws.com. Then set the ARN of that role with aws apigateway update-account. This value is shared across the entire account and region, so I did not include it in the template.

aws apigateway get-account --region ap-northeast-1
{
    "cloudwatchRoleArn": "arn:aws:iam::123456789012:role/example-role",
    "throttleSettings": {
        "burstLimit": 5000,
        "rateLimit": 10000.0
    },
    "features": [],
    "apiKeyVersion": "3"
}

For the stage, I set LoggingLevel to INFO and DataTraceEnabled to true. Unless loggingLevel is INFO or ERROR, no execution log events are generated at all. Delivery consists of three resources: a DeliverySource as the origin, a DeliveryDestination as the target, and a Delivery that connects the two. The following is an excerpt defining a CloudWatch Logs JSON destination and an S3 JSON destination.

  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref Deployment
      StageName: !Ref StageName
      MethodSettings:
        - ResourcePath: "/*"
          HttpMethod: "*"
          LoggingLevel: INFO
          DataTraceEnabled: true

  DeliverySource:
    Type: AWS::Logs::DeliverySource
    Condition: WithDelivery
    DependsOn: ApiStage
    Properties:
      Name: !Sub "${StackPrefix}-source"
      LogType: EXECUTION_LOGS
      ResourceArn: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:${AWS::AccountId}:/restapis/${RestApi}/stages/${StageName}"

  DestinationCwlJson:
    Type: AWS::Logs::DeliveryDestination
    Condition: WithDelivery
    Properties:
      Name: !Sub "${StackPrefix}-cwl-json"
      DestinationResourceArn: !GetAtt DeliveryLogGroupJson.Arn
      OutputFormat: json

  DeliveryCwlJson:
    Type: AWS::Logs::Delivery
    Condition: WithDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt DestinationCwlJson.Arn

  DestinationS3Json:
    Type: AWS::Logs::DeliveryDestination
    Condition: WithDelivery
    DependsOn: DeliveryBucketPolicy
    Properties:
      Name: !Sub "${StackPrefix}-s3-json"
      DestinationResourceArn: !GetAtt DeliveryBucket.Arn
      OutputFormat: json

  DeliveryS3Json:
    Type: AWS::Logs::Delivery
    Condition: WithDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt DestinationS3Json.Arn

First, I deployed with EnableLogDelivery set to false, enabling only the standard execution logs. The template creates a named IAM role, so specify CAPABILITY_NAMED_IAM.

aws cloudformation deploy \
  --template-file apigw-exec-logs-demo.yaml \
  --stack-name apigw-exec-logs-demo \
  --parameter-overrides EnableLogDelivery=false \
  --capabilities CAPABILITY_NAMED_IAM \
  --region ap-northeast-1

aws cloudformation describe-stacks \
  --stack-name apigw-exec-logs-demo \
  --region ap-northeast-1 \
  --query 'Stacks[0].Outputs'
Full CloudFormation template and constraints
AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  API Gateway REST API execution logs - CloudWatch Logs delivery (Vended Logs) verification stack.
  Deploy with EnableLogDelivery=false first (standard execution logging only),
  then redeploy with EnableLogDelivery=true to add the deliveries.

Parameters:
  StackPrefix:
    Type: String
    Default: apigw-exec-logs-demo
    Description: Prefix used for resource names.

  StageName:
    Type: String
    Default: prod

  EnableLogDelivery:
    Type: String
    Default: "false"
    AllowedValues: ["true", "false"]
    Description: >-
      false = standard execution logging only (API Gateway managed log group).
      true  = create delivery source / destinations / deliveries.

  LogRetentionInDays:
    Type: Number
    Default: 30

Conditions:
  WithDelivery: !Equals [!Ref EnableLogDelivery, "true"]

Resources:

  # ---------------------------------------------------------------
  # NOTE: the account-level CloudWatch role for API Gateway
  # (AWS::ApiGateway::Account / cloudWatchRoleArn) is intentionally NOT in this
  # template. It is a single account/Region-wide setting shared by every REST
  # API, so managing it here could overwrite or delete another API's setting.
  # Configure it outside CloudFormation beforehand; see verification-procedure.md
  # step 1 ("account level CloudWatch role").
  # ---------------------------------------------------------------

  # ---------------------------------------------------------------
  # Lambda (echo backend)
  # ---------------------------------------------------------------
  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${StackPrefix}-lambda-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  FunctionLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${StackPrefix}-fn"
      RetentionInDays: !Ref LogRetentionInDays

  Function:
    Type: AWS::Lambda::Function
    DependsOn: FunctionLogGroup
    Properties:
      FunctionName: !Sub "${StackPrefix}-fn"
      Runtime: python3.13
      Handler: index.lambda_handler
      Role: !GetAtt FunctionRole.Arn
      Timeout: 10
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):
              body = event.get("body") or ""
              response = {
                  "path": event.get("path"),
                  "httpMethod": event.get("httpMethod"),
                  "queryStringParameters": event.get("queryStringParameters"),
                  "receivedBodyBytes": len(body.encode("utf-8")),
                  "echo": body[:200],
              }
              return {
                  "statusCode": 200,
                  "headers": {"content-type": "application/json"},
                  "body": json.dumps(response),
              }

  FunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt Function.Arn
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/*/*"

  # ---------------------------------------------------------------
  # REST API (minimal: /echo -> Lambda proxy)
  # ---------------------------------------------------------------
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Ref StackPrefix
      Description: Execution log delivery verification API
      EndpointConfiguration:
        Types: [REGIONAL]

  EchoResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: echo

  EchoMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApi
      ResourceId: !Ref EchoResource
      HttpMethod: ANY
      # Verification-only endpoint: no authorizer. Do not copy this to production.
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"

  Deployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: EchoMethod
    Properties:
      RestApiId: !Ref RestApi

  ApiStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref RestApi
      DeploymentId: !Ref Deployment
      StageName: !Ref StageName
      MethodSettings:
        - ResourcePath: "/*"
          HttpMethod: "*"
          LoggingLevel: INFO
          DataTraceEnabled: true

  # ---------------------------------------------------------------
  # Delivery destinations (conditional)
  # ---------------------------------------------------------------
  DeliveryLogGroupJson:
    Type: AWS::Logs::LogGroup
    Condition: WithDelivery
    Properties:
      LogGroupName: !Sub "/aws/vendedlogs/apigateway/${StackPrefix}/${StageName}"
      RetentionInDays: !Ref LogRetentionInDays

  DeliveryLogGroupPlain:
    Type: AWS::Logs::LogGroup
    Condition: WithDelivery
    Properties:
      LogGroupName: !Sub "/aws/vendedlogs/apigateway/${StackPrefix}/${StageName}-plain"
      RetentionInDays: !Ref LogRetentionInDays

  DeliveryBucket:
    Type: AWS::S3::Bucket
    Condition: WithDelivery
    Properties:
      BucketName: !Sub "${StackPrefix}-${AWS::AccountId}-${AWS::Region}"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          # SSE-S3 (Amazon S3 managed keys). No KMS key is used: SSE-S3 needs no
          # extra configuration and adds no key charges.
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  # Bucket policy per "Logs sent to Amazon S3" (V2 permissions):
  # https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AWS-logs-infrastructure-V2-S3.html
  DeliveryBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Condition: WithDelivery
    Properties:
      Bucket: !Ref DeliveryBucket
      PolicyDocument:
        Version: "2012-10-17"
        Id: AWSLogDeliveryWrite20150319
        Statement:
          - Sid: AWSLogDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub "${DeliveryBucket.Arn}/AWSLogs/${AWS::AccountId}/*"
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:delivery-source:*"

  # ---------------------------------------------------------------
  # Vended Logs delivery (conditional)
  # ---------------------------------------------------------------
  DeliverySource:
    Type: AWS::Logs::DeliverySource
    Condition: WithDelivery
    # PutDeliverySource fails unless execution logging is already enabled on the
    # stage, so the stage must exist with LoggingLevel INFO/ERROR first.
    DependsOn: ApiStage
    Properties:
      Name: !Sub "${StackPrefix}-source"
      LogType: EXECUTION_LOGS
      ResourceArn: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:${AWS::AccountId}:/restapis/${RestApi}/stages/${StageName}"

  # (1) own CloudWatch Logs log group, structured JSON (all record fields)
  DestinationCwlJson:
    Type: AWS::Logs::DeliveryDestination
    Condition: WithDelivery
    Properties:
      Name: !Sub "${StackPrefix}-cwl-json"
      DestinationResourceArn: !GetAtt DeliveryLogGroupJson.Arn
      OutputFormat: json

  DeliveryCwlJson:
    Type: AWS::Logs::Delivery
    Condition: WithDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt DestinationCwlJson.Arn

  # (2) S3 bucket, JSON
  DestinationS3Json:
    Type: AWS::Logs::DeliveryDestination
    Condition: WithDelivery
    DependsOn: DeliveryBucketPolicy
    Properties:
      Name: !Sub "${StackPrefix}-s3-json"
      DestinationResourceArn: !GetAtt DeliveryBucket.Arn
      OutputFormat: json

  DeliveryS3Json:
    Type: AWS::Logs::Delivery
    Condition: WithDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt DestinationS3Json.Arn

  # (3) own log group, payload only (equivalent to standard execution log text).
  #     The CLI guidance uses --field-delimiter "" (empty string), but the
  #     CloudFormation property has a minimum length of 1, so it is omitted here.
  #     Verify whether RecordFields: [payload] alone reproduces the plain output.
  DestinationCwlPlain:
    Type: AWS::Logs::DeliveryDestination
    Condition: WithDelivery
    Properties:
      Name: !Sub "${StackPrefix}-cwl-plain"
      DestinationResourceArn: !GetAtt DeliveryLogGroupPlain.Arn
      OutputFormat: plain

  DeliveryCwlPlain:
    Type: AWS::Logs::Delivery
    Condition: WithDelivery
    Properties:
      DeliverySourceName: !Ref DeliverySource
      DeliveryDestinationArn: !GetAtt DestinationCwlPlain.Arn
      RecordFields:
        - payload

Outputs:
  ApiInvokeUrl:
    Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}"
  RestApiId:
    Value: !Ref RestApi
  StageArn:
    Value: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:${AWS::AccountId}:/restapis/${RestApi}/stages/${StageName}"
  AutoManagedLogGroupName:
    Description: API Gateway managed log group (not managed by this stack; delete manually at teardown)
    Value: !Sub "API-Gateway-Execution-Logs_${RestApi}/${StageName}"
  DeliveryLogGroupJsonName:
    Condition: WithDelivery
    Value: !Ref DeliveryLogGroupJson
  DeliveryLogGroupPlainName:
    Condition: WithDelivery
    Value: !Ref DeliveryLogGroupPlain
  DeliveryBucketName:
    Condition: WithDelivery
    Value: !Ref DeliveryBucket

The DeliverySource must be created after the ApiStage is created. In a template without DependsOn, the DeliverySource ended up as CREATE_FAILED. The error was Invalid stage identifier specified. You cannot register a delivery source while execution logging is not enabled on the stage.

The S3 destination DestinationS3Json is created after the bucket policy is applied. The destination log group name does not need to be under /aws/vendedlogs/. put-delivery-destination and create-delivery succeeded with /apigw-exec-logs-demo/v4-prefix-test as the destination. When RecordFields is omitted, the following seven fields are selected by default.

{
    "delivery": {
        "id": "<delivery-id>",
        "deliverySourceName": "apigw-exec-logs-demo-source",
        "deliveryDestinationType": "CWL",
        "recordFields": [
            "resource_arn",
            "event_timestamp",
            "api_id",
            "stage",
            "resource_path",
            "http_method",
            "payload"
        ]
    }
}

Execution Logs Before Delivery

To check truncation in the auto-managed log group before delivery, I ran a GET, a POST with a small body, a POST with 4KB, and a GET to a nonexistent path. The first three returned 200, and the nonexistent path returned 403. The 4KB body was generated with the following script, which lines up 55 dummy values of 40 characters each.

#!/usr/bin/env bash
# Generate a ~4KB JSON request body used to check the 1 KB truncation of
# standard execution logging. Dummy values only (no PII, no real domains).
#
#   bash artifacts/make-payload-4kb.sh > artifacts/payload-4kb.json
#   wc -c artifacts/payload-4kb.json
set -euo pipefail

n=${1:-55}

printf '{"note":"execution log truncation check","items":['
for i in $(seq 1 "$n"); do
  [ "$i" -gt 1 ] && printf ','
  printf '{"id":%d,"key":"item-%03d","value":"%s"}' \
    "$i" "$i" "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd"
done
printf ']}\n'
bash make-payload-4kb.sh > payload-4kb.json

BASE=https://<rest-api-id>.execute-api.ap-northeast-1.amazonaws.com/prod
curl -s -i "$BASE/echo?name=kiro"
curl -s -i -X POST "$BASE/echo" -H 'content-type: application/json' -d '{"msg":"small"}'
curl -s -i -X POST "$BASE/echo" -H 'content-type: application/json' --data-binary @payload-4kb.json
curl -s -i "$BASE/notfound"

For the request that sent 4,333 bytes of JSON as the body, the Method request body before transformations event was 1,036 bytes. [TRUNCATED] was appended at the end. Endpoint request headers and Endpoint request body after transformations were also truncated to the same 1,036 bytes.

(05dd036b-14c9-4207-aa4f-0875c1a5c39a) Method request body before transformations: {"note":"execution log truncation check","items":[{"id":1,"key":"item-001","value":"aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd"},{"id":2,"key":"item-002","value":"aaaaaaaaaabbbbbb
(中略)
aaabbbbbbbbbbccccccccccdddddddddd"},{"id":12,"key":"item-012","value":"aaaaaaa [TRUNCATED]

Adding the Delivery

I redeployed the same template with EnableLogDelivery=true. Two log groups, an S3 bucket, one DeliverySource, three DeliveryDestinations, and three Deliveries were added.

aws cloudformation deploy \
  --template-file apigw-exec-logs-demo.yaml \
  --stack-name apigw-exec-logs-demo \
  --parameter-overrides EnableLogDelivery=true \
  --capabilities CAPABILITY_NAMED_IAM \
  --region ap-northeast-1
aws logs describe-deliveries --region ap-northeast-1
delivery ID Destination OutputFormat recordFields
<delivery-id-1> Own log group json Not specified (default 7 fields)
<delivery-id-2> Own log group (for plain) plain payload
<delivery-id-3> S3 bucket json Not specified (default 7 fields)

From a single DeliverySource, logs were delivered in parallel to CloudWatch Logs in JSON format, S3 in JSON format, and CloudWatch Logs in plain format.

How Logs Look at Each Destination

For the POST request that sent the 4KB body, a structured record arrived at the JSON-format CloudWatch Logs destination. The whole event was 5,198 bytes, and [TRUNCATED] was not appended to the payload.

{
  "resource_arn": "arn:aws:apigateway:ap-northeast-1:123456789012:/restapis/<rest-api-id>/stages/prod",
  "event_timestamp": 1789278878697,
  "api_id": "<rest-api-id>",
  "stage": "prod",
  "resource_path": "/echo",
  "http_method": "POST",
  "payload": "(783aca98-1241-4a3a-a73c-b996b19046df) Method request body before transformations: {\"note\":\"execution log truncation check\",\"items\":[{\"id\":1,\"key\":\"item-001\",\"value\":\"aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd\"},(中略)]}"
}

For the delivery where I specified only payload in RecordFields and chose the plain format, only the same text as the conventional execution logs arrived as one event. You can choose this format when using a parser that assumes the existing text format.

(dc8caeee-4a2c-4829-accd-576372439c69) Starting execution for request: dc8caeee-4a2c-4829-accd-576372439c69
(dc8caeee-4a2c-4829-accd-576372439c69) HTTP Method: GET, Resource Path: /echo
(dc8caeee-4a2c-4829-accd-576372439c69) Method request query string: {name=kiro}

I compared the Method request body before transformations for the same request before and after delivery.

Destination Event size (bytes) Truncation
Auto-managed log group (before delivery) 1,036 Yes ([TRUNCATED])
Own log group (json delivery) 5,198 No
Own log group (plain delivery, payload only) 4,416 No

With this 4KB request, the body was not truncated in either the JSON or the plain delivery. The output format can be chosen per destination. For CloudWatch Logs destinations it is json / plain, for S3 destinations json / plain / w3c / parquet, and for Firehose destinations json / plain. For details, see the official documentation on log delivery.

In the JSON delivery log group, I could search by specifying the fields added by delivery by name. The first query returned 20 rows, and the second query returned 3 rows: the 4KB POST, the small POST, and the GET.

fields @timestamp, api_id, stage, http_method, resource_path, @ingestionTime
| sort @timestamp desc
| limit 20
filter payload like /Method request body/
| fields @timestamp, resource_path, http_method, payload
| sort @timestamp desc
| limit 5
@timestamp resource_path http_method
2026-09-13 05:54:38.697 /echo POST
2026-09-13 05:54:38.610 /echo POST
2026-09-13 05:54:38.327 /echo GET

In S3, objects were stored under the hierarchy AWSLogs/{account ID}/APIGateway/ExecutionLogs/{region}/{REST API ID}/{stage}/. The file name includes the date and time, and a gzip-compressed 6,909-byte object contained 57 records.

AWSLogs/123456789012/APIGateway/ExecutionLogs/ap-northeast-1/<rest-api-id>/prod/2026-09-13-05.APIGateway_<rest-api-id>_prod_2026-09-13-05_c4120841.log.gz

The decompressed file was JSON with one record per line, and its fields were the same as in the JSON delivery to CloudWatch Logs.

{"resource_arn":"arn:aws:apigateway:ap-northeast-1:123456789012:/restapis/<rest-api-id>/stages/prod","event_timestamp":1789278878606,"api_id":"<rest-api-id>","stage":"prod","resource_path":"/echo","http_method":"POST","payload":"(012d5149-bf27-4d3e-92c6-ba0e45aa3711) Extended Request Id: Dn4Y3HADNjMEKZA="}

Using the time the curl was executed (2026-09-13T05:54:38Z) as the starting point, I also checked the first arrival time at each destination.

Destination Value used to determine arrival time Arrival time Time from start
Own log group (json) @ingestionTime 2026-09-13T05:54:52Z About 14 seconds
S3 bucket Object LastModified 2026-09-13T05:59:09Z About 4 min 31 sec

In this test, the CloudWatch Logs destination received the logs first. Note that the official documentation states explicitly that execution log delivery is best-effort and logs may not arrive.

Writes to the Auto-Managed Log Group Stop

For the four requests executed after creating the delivery, I filtered the auto-managed log group to the period after the curl execution time and checked it.

aws logs filter-log-events \
  --log-group-name "API-Gateway-Execution-Logs_<rest-api-id>/prod" \
  --start-time 1789278878000 \
  --region ap-northeast-1
{
    "events": [],
    "searchedLogStreams": []
}

There were 0 matching events. When you configure CloudWatch Logs delivery, API Gateway stops writing to the auto-managed log group. If existing alarms or subscription filters reference the auto-managed log group, migrate them before switching, or include the same log group among the delivery destinations.

Pricing

CloudWatch Logs delivery is charged at tiered Vended Logs rates based on monthly data volume. The following unit prices are the values in the N. Virginia calculation example on the CloudWatch pricing page. Tiers reset every month.

Monthly data volume Unit price
0–10TB $0.50/GB
10–30TB $0.25/GB
30–50TB $0.10/GB
Over 50TB $0.05/GB

The amounts in the table are delivery charges. Even when the destination is S3, delivery charges apply, and they appear on the bill as <region>-S3-Egress-Bytes. S3 storage charges and charges for optional format conversion such as Apache Parquet are not included in the table amounts. For Firehose destinations, Firehose ingestion charges are also incurred separately.

The conventional auto-managed log group is charged at standard CloudWatch Logs ingestion rates. In the N. Virginia, Standard class calculation example on the same pricing page, ingestion was $0.50/GB and storage was $0.03/GB. The first tier of Vended Logs is also $0.50/GB, so the unit prices are at a similar level.

Cost differences arise from the total bytes of billable logs and the storage costs at the destination. In the auto-managed log group, events are truncated at 1KB, whereas delivery records up to 1MB. For this 4KB request, the auto-managed log group had 1,036 bytes, whereas the JSON delivery had 5,198 bytes.

Because the Vended Logs pricing structure applies to CloudWatch Logs delivery, unit prices and tiers may differ from the conventional method. In addition, the truncation limit was raised from 1 KB to 1 MB, and outputting in JSON format also increases the record size, so the log volume may increase. In environments that generate a lot of logs, I recommend checking that you can use it at the expected cost.

Summary

With CloudWatch Logs delivery, API Gateway REST API execution logs, which were previously truncated at 1KB and could only be output to CloudWatch Logs, can now be recorded up to 1MB, delivered to S3 and Firehose, and saved in formats such as JSON and Parquet.
Saving in JSON format lets you use Logs Insights searches that specify fields in CloudWatch Logs, as well as Log Based Alarms based on the results of those queries, and makes analysis with Athena and similar tools easier in S3.
However, if the volume of logs recorded as Vended Logs increases, costs may rise compared with the conventional method, so I recommend checking after setup that no unexpected costs are occurring.

Share this article

AWSのお困り事はクラスメソッドへ