API Gateway supports CloudWatch Logs delivery. I tried delivery to S3 and CloudWatch Logs.

API Gateway supports CloudWatch Logs delivery. I tried delivery to S3 and CloudWatch Logs.

CloudWatch Logs delivery now supports API Gateway REST API execution logs as a log source. Previously, logs exceeding 1 KB were truncated, but they can now be recorded up to a maximum of 1 MB. In addition to delivery to CloudWatch Logs and S3, integration with Firehose and storage in JSON and Parquet formats are also now supported.
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 allows recording events up to 1MB. Payloads exceeding 1MB continue to be truncated.

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

I configured both the traditional execution logs and this new delivery method, and compared how logs for the same request appear.

Verification Details

I created a minimal REST API and Lambda using CloudFormation, then added configuration to deliver execution logs to both CloudWatch Logs and S3. I sent the same requests before and after adding the delivery, and reviewed the logs in both the automatically managed log group and each delivery destination.

Verification Environment

Since differences between CloudFormation property constraints and values accepted by the CLI affect results, I am noting the environment used.

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, cloudWatchRoleArn must be configured per account and region. In this verification account, I reused an already-configured role. If not yet configured, grant AmazonAPIGatewayPushToCloudWatchLogs to a role that trusts apigateway.amazonaws.com. Set the ARN of that role using aws apigateway update-account. Since this value is shared across the entire account and region, it is not included 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"
}

In the stage, I set LoggingLevel to INFO and DataTraceEnabled to true. Unless loggingLevel is INFO or ERROR, execution log events will not be generated at all. A delivery consists of three resources: a DeliverySource as the source, a DeliveryDestination as the destination, and a Delivery connecting the two. Below is an excerpt defining a JSON destination for CloudWatch Logs and a JSON destination for S3.

  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 to enable only standard execution logs. Since the template creates named IAM roles, CAPABILITY_NAMED_IAM must be specified.

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

DeliverySource must be created after ApiStage. In a template without DependsOn, DeliverySource became CREATE_FAILED. The error was Invalid stage identifier specified. The delivery source cannot be registered when execution logging is not enabled on the stage.

DestinationS3Json for S3 is created after the bucket policy is applied. The destination log group name does not need to be under /aws/vendedlogs/. A put-delivery-destination and create-delivery targeting /apigw-exec-logs-demo/v4-prefix-test succeeded. When RecordFields is omitted, the following 7 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 confirm truncation in the automatically managed log group before delivery, I sent a GET, a POST with a small body, a POST with a 4KB body, and a GET to a non-existent path. The first three returned 200, and the non-existent path returned 403. The 4KB body was generated using the following script, which concatenates 55 items each with a 40-character dummy value.

#!/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 a 4,333-byte JSON 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
(omitted)
aaabbbbbbbbbbccccccccccdddddddddd"},{"id":12,"key":"item-012","value":"aaaaaaa [TRUNCATED]

Adding Delivery

I redeployed using the same template with EnableLogDelivery=true. Two log groups, one 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, and CloudWatch Logs in plain format.

How It Looks at the Destination

For a POST request with a 4KB body, a structured record arrived in JSON format at CloudWatch Logs. The entire event was 5,198 bytes, and no [TRUNCATED] was 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\"},(中略)]}"
}

In a delivery where only payload was specified in RecordFields and the plain format was selected, only the same text as traditional execution logs arrived as a single event. This format can also be selected when using a parser that expects 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}

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

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

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

In the log group with JSON delivery, fields assigned during delivery could be searched 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/{AccountID}/APIGateway/ExecutionLogs/{Region}/{REST API ID}/{Stage}/. The file name includes a timestamp, and a gzip-compressed object of 6,909 bytes 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 extracted file was JSON with one record per line, and the 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 of the curl execution (2026-09-13T05:54:38Z) as a starting point, the first arrival time at each destination was also verified.

Destination Value used to determine arrival time Arrival time Time from starting point
Custom log group (json) @ingestionTime 2026-09-13T05:54:52Z Approx. 14 seconds
S3 bucket Object LastModified 2026-09-13T05:59:09Z Approx. 4 minutes 31 seconds

For this request, CloudWatch Logs arrived first. Note that execution log delivery is best-effort, and it is explicitly stated in the official documentation that logs may not always be delivered.

Stopping Logging to the Auto-Managed Log Group

For the four requests executed after creating the delivery, the auto-managed log group was checked for entries from the time of the curl execution onward.

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 CloudWatch Logs delivery is configured, 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 as a delivery destination.

Pricing

CloudWatch Logs delivery is charged at tiered Vended Logs rates based on monthly data volume. The unit prices below are from the calculation example for Northern Virginia 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. Delivery charges apply even when the destination is S3, and they appear in billing as <Region>-S3-Egress-Bytes. S3 storage charges and charges for optional format conversion such as Apache Parquet are not included in the amounts in the table. For Firehose destinations, Firehose ingestion charges also apply separately.

The traditional auto-managed log group uses standard CloudWatch Logs ingestion pricing. In the calculation example for Northern Virginia Standard class 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 price is at the same level.

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

Since CloudWatch Logs delivery applies the Vended Logs pricing model, the unit price and tiers may differ from the traditional method. Additionally, since the truncation limit for logs is raised from 1KB to 1MB, and outputting in JSON format also increases record size, log volume may increase. In environments where large amounts of logs are generated, it is recommended to verify whether usage within the expected cost is achievable.

Summary

CloudWatch Logs delivery enables API Gateway REST API execution logs—which were previously truncated at 1KB and limited to CloudWatch Logs as the only output destination—to be recorded up to 1MB, delivered to S3 or Firehose, and saved in JSON or Parquet format.
When saved in JSON format, CloudWatch Logs enables Logs Insights searches with specified fields and Log Based Alarms based on those query results, while S3 makes it easier to perform analysis with tools such as Athena.
However, if the volume of logs recorded as Vended Logs increases, costs may be higher than with the traditional method, so it is recommended to verify after configuration that no unexpected costs are occurring.

Share this article

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