I Tried Configuring ALB's CloudWatch Logs Integrated Logging with CloudFormation

I Tried Configuring ALB's CloudWatch Logs Integrated Logging with CloudFormation

I wrote CloudFormation to deliver ALB access, connection, and health check logs to S3, and access logs to CloudWatch Logs and Amazon Data Firehose. I also checked how the template behaves when RecordFields is specified.
2026.07.28

This page has been translated by machine translation. View original

Introduction

On July 23, 2026, Application Load Balancer (ALB) logs gained support for CloudWatch Logs integrated logging (Vended Logs). The supported log types are three: access logs, connection logs, and health check logs. You can choose S3, CloudWatch Logs, or Amazon Data Firehose as the delivery destination.

https://aws.amazon.com/jp/about-aws/whats-new/2026/07/amazon-cloudwatch-logs/

https://dev.classmethod.jp/articles/alb-access-logs-cloudwatch-logs-vended/

The third link is the previous article, which covers the differences from the legacy method (S3 delivery via ALB attributes) and the setup procedure with the CLI.

In this article, I described the three resources Delivery Source / Delivery Destination / Delivery in CloudFormation. I configured all three destinations, S3, CloudWatch Logs, and Firehose, at the same time, and also checked the points to be careful about when specifying RecordFields, since all fields are mandatory.

What I Verified

Verification Environment

Item Value
Region ap-northeast-1
ALB internet-facing, two listeners: HTTP:80 and HTTPS:443
Origin EC2 (Amazon Linux 2023, nginx installed via UserData)
Security group ALB allows only one address, the one the verification was run from
Log types ALB_ACCESS_LOGS / ALB_CONNECTION_LOGS / ALB_HEALTH_CHECK_LOGS

I added the HTTPS listener so that TLS-related fields would appear in the connection logs.

For the legacy S3 delivery via ALB attributes, I left all three disabled as follows. All log output in this verification goes through integrated logging.

access_logs.s3.enabled        = false
health_check_logs.s3.enabled  = false
connection_logs.s3.enabled    = false

I split the resources into two templates: a base template and a delivery template.

Resource Where it is created
ALB / EC2 / target group / listeners Base template (only the configuration is described in this article)
S3 bucket and bucket policy Delivery template
CloudWatch Logs log group Delivery template
Firehose delivery stream and execution role Delivery template
Delivery Source / Destination / Delivery Delivery template
ACM certificate (for the HTTPS listener) Created in advance. Passed as a parameter from outside the template

The Firehose delivery stream is of the DirectPut type, and it writes to a different prefix in the same S3 bucket downstream. The buffer is 60 seconds / 5 MB, with GZIP compression and no record transformation.

Configuring the Three Destinations with CloudFormation

AWS::Logs::DeliverySource represents the origin of the logs (the ALB and the log type), and AWS::Logs::DeliveryDestination represents the destination (S3 bucket, log group, or Firehose stream). These two are linked by AWS::Logs::Delivery.

Full delivery template
AWSTemplateFormatVersion: '2010-09-09'
Description: ALB CloudWatch Logs integrated logging - delivery to S3 / CloudWatch Logs / Firehose

Parameters:
  LoadBalancerArn:
    Type: String
    Description: ARN of the target ALB
  BucketName:
    Type: String
    Description: S3 bucket that receives the vended logs
  BillingGroupPrefix:
    Type: String
    Default: kiro-alb-cwl
    Description: Prefix of the CmBillingGroup cost allocation tag values

Resources:

  # ---------------------------------------------------------------
  # Destination 1: S3
  # ---------------------------------------------------------------
  LogsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-s3-bucket

  LogsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref LogsBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AWSLogsDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub arn:aws:s3:::${BucketName}/*
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
          - Sid: AWSLogsDeliveryAclCheck
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !Sub arn:aws:s3:::${BucketName}
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId

  S3Destination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: alb-logs-s3-destination
      DeliveryDestinationType: S3
      OutputFormat: json
      DestinationResourceArn: !GetAtt LogsBucket.Arn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dest-s3

  # ---------------------------------------------------------------
  # Destination 2: CloudWatch Logs
  # ---------------------------------------------------------------
  AccessLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /aws/vendedlogs/alb-access-logs
      RetentionInDays: 1
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-loggroup

  CwlDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: alb-logs-cwl-destination
      DeliveryDestinationType: CWL
      OutputFormat: json
      DestinationResourceArn: !GetAtt AccessLogGroup.Arn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dest-cwl

  # ---------------------------------------------------------------
  # Destination 3: Data Firehose
  # ---------------------------------------------------------------
  FirehoseToS3Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: write-to-logs-bucket
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:AbortMultipartUpload
                  - s3:GetBucketLocation
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:ListBucketMultipartUploads
                  - s3:PutObject
                Resource:
                  - !Sub arn:aws:s3:::${BucketName}
                  - !Sub arn:aws:s3:::${BucketName}/*
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-iam

  FirehoseStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamName: alb-logs-firehose
      DeliveryStreamType: DirectPut
      ExtendedS3DestinationConfiguration:
        BucketARN: !Sub arn:aws:s3:::${BucketName}
        RoleARN: !GetAtt FirehoseToS3Role.Arn
        Prefix: firehose/access-logs/
        ErrorOutputPrefix: firehose/errors/
        CompressionFormat: GZIP
        BufferingHints:
          IntervalInSeconds: 60
          SizeInMBs: 5
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-firehose

  FirehoseDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: alb-logs-firehose-destination
      DeliveryDestinationType: FH
      OutputFormat: json
      DestinationResourceArn: !GetAtt FirehoseStream.Arn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dest-firehose

  # ---------------------------------------------------------------
  # Delivery sources (one per log type)
  # ---------------------------------------------------------------
  AccessLogSource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: alb-access-logs
      LogType: ALB_ACCESS_LOGS
      ResourceArn: !Ref LoadBalancerArn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-source

  ConnectionLogSource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: alb-connection-logs
      LogType: ALB_CONNECTION_LOGS
      ResourceArn: !Ref LoadBalancerArn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-source

  HealthCheckLogSource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: alb-health-check-logs
      LogType: ALB_HEALTH_CHECK_LOGS
      ResourceArn: !Ref LoadBalancerArn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-source

  # ---------------------------------------------------------------
  # Deliveries (source x destination)
  # RecordFields is omitted, so the service default field set and
  # order are used.
  # ---------------------------------------------------------------
  AccessLogToS3:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref AccessLogSource
      DeliveryDestinationArn: !GetAtt S3Destination.Arn
      S3SuffixPath: access-logs/{account-id}/{region}/{yyyy}/{MM}/{dd}/
      S3EnableHiveCompatiblePath: false
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dlv-s3-access

  ConnectionLogToS3:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref ConnectionLogSource
      DeliveryDestinationArn: !GetAtt S3Destination.Arn
      S3SuffixPath: connection-logs/{account-id}/{region}/{yyyy}/{MM}/{dd}/
      S3EnableHiveCompatiblePath: false
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dlv-s3-connection

  HealthCheckLogToS3:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref HealthCheckLogSource
      DeliveryDestinationArn: !GetAtt S3Destination.Arn
      S3SuffixPath: health-check-logs/{account-id}/{region}/{yyyy}/{MM}/{dd}/
      S3EnableHiveCompatiblePath: false
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dlv-s3-healthcheck

  AccessLogToCwl:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref AccessLogSource
      DeliveryDestinationArn: !GetAtt CwlDestination.Arn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dlv-cwl-access

  AccessLogToFirehose:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref AccessLogSource
      DeliveryDestinationArn: !GetAtt FirehoseDestination.Arn
      Tags:
        - Key: CmBillingGroup
          Value: !Sub ${BillingGroupPrefix}-dlv-fh-access

Outputs:
  BucketName:
    Value: !Ref LogsBucket
  LogGroupName:
    Value: !Ref AccessLogGroup
  FirehoseStreamName:
    Value: !Ref FirehoseStream

Even with three kinds of destinations, the way to write AWS::Logs::DeliveryDestination itself is almost the same. The main properties that differ per destination in this template are DeliveryDestinationType and DestinationResourceArn. In addition, the settings of the surrounding resources differ among S3, CloudWatch Logs, and Firehose.

First, DeliveryDestinationType. The value to specify is S3 for S3, CWL for CloudWatch Logs, and FH for Firehose. Notations such as CloudWatchLogs or Firehose are not accepted.

  S3Destination:
    Properties:
      DeliveryDestinationType: S3

  CwlDestination:
    Properties:
      DeliveryDestinationType: CWL

  FirehoseDestination:
    Properties:
      DeliveryDestinationType: FH

Next, DestinationResourceArn. You pass the ARN of the bucket for S3, the log group for CloudWatch Logs, and the delivery stream for Firehose. In every case, resources created in the same template can be referenced with !GetAtt, so there is no need to assemble the ARN as a string.

  S3Destination:
    Properties:
      DestinationResourceArn: !GetAtt LogsBucket.Arn

  CwlDestination:
    Properties:
      DestinationResourceArn: !GetAtt AccessLogGroup.Arn

  FirehoseDestination:
    Properties:
      DestinationResourceArn: !GetAtt FirehoseStream.Arn

For S3 destinations, the bucket policy allows s3:PutObject and s3:GetBucketAcl for delivery.logs.amazonaws.com. The caller is restricted with aws:SourceAccount and aws:SourceArn.

          - Sid: AWSLogsDeliveryWrite
            Effect: Allow
            Principal:
              Service: delivery.logs.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub arn:aws:s3:::${BucketName}/*
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*

For CloudWatch Logs destinations, the log group needs a resource policy that allows logs:CreateLogStream and logs:PutLogEvents for delivery.logs.amazonaws.com. This template does not explicitly define an AWS::Logs::ResourcePolicy. If the person doing the setup has the necessary permissions, AWS automatically configures this policy when the Delivery is created (Logs sent to CloudWatch Logs).

Note that an execution role is required for the Firehose delivery stream itself to write to the downstream S3, and it is defined as FirehoseToS3Role in the template.

On the other hand, a custom role trusting delivery.logs.amazonaws.com was not needed for the Firehose destination. Even after I deleted that role, which I had initially prepared in the template, and updated the stack, delivery to all three destinations continued. CloudWatch Logs uses the service-linked role AWSServiceRoleForLogDelivery for direct writes to Firehose (Using service-linked roles for CloudWatch Logs).

Separately from permissions, cost allocation tags can be set on all three resources: Delivery Source, Delivery Destination, and Delivery. To keep track of the cost of the CloudWatch Logs integration, I applied CmBillingGroup to all resources in the template this time.

The list of deliveries checked with aws logs describe-deliveries after deployment is as follows.

Delivery ID Delivery Source Delivery Destination Type recordFields
aaaa1111bbbb2222 alb-access-logs alb-logs-s3-destination S3 Not specified
cccc3333dddd4444 alb-connection-logs alb-logs-s3-destination S3 Not specified
eeee5555ffff6666 alb-health-check-logs alb-logs-s3-destination S3 Not specified
gggg7777hhhh8888 alb-access-logs alb-logs-cwl-destination CWL Not specified
iiii9999jjjj0000 alb-access-logs alb-logs-firehose-destination FH Not specified

The access log Delivery Source alb-access-logs is linked to the three destinations S3, CWL, and FH. A single Delivery Source can be linked to multiple destinations and deliver in parallel.

Checking the Logs That Arrived at the Three Destinations

I sent requests to the ALB over HTTP and HTTPS and checked the logs that arrived at each of the three destinations. OutputFormat is set to json for all of them.

In S3, an object was created with the following key.

AWSLogs/123456789012/elasticloadbalancing/access-logs/123456789012/ap-northeast-1/2026/07/27/
  123456789012_elasticloadbalancing_ap-northeast-1_app.example-alb.0123456789abcdef_20260727T1450Z_198.51.100.10_0a1b2c3d.log.gz

The leading AWSLogs/<account ID>/elasticloadbalancing/ is added by the service side. Because I included {account-id} in S3SuffixPath, the account ID appears twice in one key. To avoid the duplication, remove {account-id} from S3SuffixPath.

Access log that arrived in S3 (HTTPS request)
{"request_line":"GET https://example-alb-1234567890.ap-northeast-1.elb.amazonaws.com:443/secure-1 HTTP/2.0","user_agent":"curl/8.18.0","trace_id":"Root=1-68a1b2c3-4d5e6f708192a3b4c5d6e7f8","domain_name":"example-alb-1234567890.ap-northeast-1.elb.amazonaws.com","chosen_cert_arn":"arn:aws:acm:ap-northeast-1:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee","actions_executed":"forward","redirect_url":"-","error_reason":"-","target_port_list":"10.0.0.100:80","target_status_code_list":"404","classification":"-","classification_reason":"-","transformed_host":"-","transformed_uri":"-","request_transform_status":"-","type":"h2","time":"2026-07-27T14:51:41.522397Z","elb":"app/example-alb/0123456789abcdef","client_port":"203.0.113.10:4985","target_port":"10.0.0.100:80","request_processing_time":"0.002","target_processing_time":"0.002","response_processing_time":"0.000","elb_status_code":"404","target_status_code":"404","received_bytes":"79","sent_bytes":"3565","ssl_cipher":"ECDHE-RSA-AES128-GCM-SHA256","ssl_protocol":"TLSv1.2","target_group_arn":"arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/example-tg/fedcba9876543210","matched_rule_priority":"0","request_creation_time":"2026-07-27T14:51:41.518000Z","conn_trace_id":"TID_a1b2c3d4e5f60718293a4b5c6d7e8f90","ip_address":"198.51.100.10"}

In CloudWatch Logs, records arrived in the log group /aws/vendedlogs/alb-access-logs created by the template. One record is stored as one event.

Access log that arrived in CloudWatch Logs (HTTP request)
{"request_line":"GET http://example-alb-1234567890.ap-northeast-1.elb.amazonaws.com:80/ HTTP/1.1","user_agent":"curl/8.18.0","trace_id":"Root=1-68a1b2c3-4d5e6f708192a3b4c5d6e7f8","domain_name":"-","chosen_cert_arn":"-","actions_executed":"forward","redirect_url":"-","error_reason":"-","target_port_list":"10.0.0.100:80","target_status_code_list":"200","classification":"-","classification_reason":"-","transformed_host":"-","transformed_uri":"-","request_transform_status":"-","type":"http","time":"2026-07-27T14:47:06.948295Z","elb":"app/example-alb/0123456789abcdef","client_port":"203.0.113.10:4988","target_port":"10.0.0.100:80","request_processing_time":"0.002","target_processing_time":"0.003","response_processing_time":"0.000","elb_status_code":"200","target_status_code":"200","received_bytes":"133","sent_bytes":"296","ssl_cipher":"-","ssl_protocol":"-","target_group_arn":"arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/example-tg/fedcba9876543210","matched_rule_priority":"0","request_creation_time":"2026-07-27T14:47:06.943000Z","conn_trace_id":"TID_a1b2c3d4e5f60718293a4b5c6d7e8f90","ip_address":"198.51.100.11"}

I checked Firehose delivery through the S3 objects specified downstream of the stream (under firehose/access-logs/2026/07/27/14/). The record field structure is the same as at the other destinations.

Access log that arrived in S3 via Firehose (HTTPS request)
{"request_line":"GET https://example-alb-1234567890.ap-northeast-1.elb.amazonaws.com:443/secure-13 HTTP/2.0","user_agent":"curl/8.18.0","trace_id":"Root=1-68a1b2c3-4d5e6f708192a3b4c5d6e7f8","domain_name":"example-alb-1234567890.ap-northeast-1.elb.amazonaws.com","chosen_cert_arn":"arn:aws:acm:ap-northeast-1:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee","actions_executed":"forward","redirect_url":"-","error_reason":"-","target_port_list":"10.0.0.100:80","target_status_code_list":"404","classification":"-","classification_reason":"-","transformed_host":"-","transformed_uri":"-","request_transform_status":"-","type":"h2","time":"2026-07-27T14:51:42.746886Z","elb":"app/example-alb/0123456789abcdef","client_port":"203.0.113.10:4985","target_port":"10.0.0.100:80","request_processing_time":"0.000","target_processing_time":"0.000","response_processing_time":"0.000","elb_status_code":"404","target_status_code":"404","received_bytes":"79","sent_bytes":"3565","ssl_cipher":"ECDHE-RSA-AES128-GCM-SHA256","ssl_protocol":"TLSv1.2","target_group_arn":"arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/example-tg/fedcba9876543210","matched_rule_priority":"0","request_creation_time":"2026-07-27T14:51:42.746000Z","conn_trace_id":"TID_a1b2c3d4e5f60718293a4b5c6d7e8f90","ip_address":"198.51.100.10"}

In the connection logs that arrived in S3, records for the HTTPS listener (listener_port of 443) had values in tls_protocol and tls_cipher.

{"leaf_client_cert_subject":"-","time":"2026-07-27T14:47:07.123363Z","client_ip":"203.0.113.10","client_port":"4985","listener_port":"443","tls_protocol":"TLSv1.2","tls_cipher":"ECDHE-RSA-AES128-GCM-SHA256","tls_handshake_latency":"0.015","leaf_client_cert_validity":"-","leaf_client_cert_serial_number":"-","tls_verify_status":"Success","conn_trace_id":"TID_a1b2c3d4e5f60718293a4b5c6d7e8f90","tls_keyexchange":"secp256r1","elb":"app/example-alb/0123456789abcdef","ip_address":"198.51.100.10"}

Checking the target ALB log types with describe-configuration-templates, the values that can be specified for OutputFormat for each destination type were as follows.

Destination Selectable formats
S3 plain / json / w3c / parquet
CloudWatch Logs plain / json
Firehose plain / json / raw

In this verification, charges may be incurred for the ALB and EC2, the Firehose delivery stream, Vended Logs delivery to CloudWatch Logs and Firehose, and storage in S3 and CloudWatch Logs. Delivery from ALB integrated logging to S3 itself is free. I deleted the stack after checking.

Points to Note When Specifying RecordFields

With RecordFields of AWS::Logs::Delivery, you can specify the fields to output. For the three ALB log types checked in ap-northeast-1 this time, all fields were mandatory. As a result, in the configuration at this point, RecordFields can effectively only be used to control output order. The field definitions confirmed with aws logs describe-configuration-templates for the three ALB log types are as follows.

Log type Number of fields Of which mandatory
ALB_ACCESS_LOGS 34 34
ALB_CONNECTION_LOGS 15 15
ALB_HEALTH_CHECK_LOGS 10 10

All fields of every log type were mandatory: true. In the configuration I checked at this point, you cannot pick and choose fields.

When I actually deployed with only two of the 10 fields of ALB_HEALTH_CHECK_LOGS, time and status_code, the ResourceStatusReason in the stack events was the following error.

Resource handler returned message: "Mandatory record fields are missing
(Service: CloudWatchLogs, Status Code: 400, Request ID: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)"
(HandlerErrorCode: InvalidRequest)

Running the same specification with aws logs create-delivery also failed in the same way.

An error occurred (ValidationException) when calling the CreateDelivery operation:
Mandatory record fields are missing

If you do not write RecordFields, the output uses the service's default field set and order. Since fields cannot be picked and chosen in the configuration I checked, the options in the template are two: "list all fields to control the order" or "do not write the RecordFields property at all".

The remaining use is controlling output order. Separately from the template shown above, I added a Destination with OutputFormat of plain and a Delivery listing RecordFields, and delivered the 10 fields of ALB_HEALTH_CHECK_LOGS in both the official default order and a swapped order.

Delivery ID Destination log group Format RecordFields
kkkk1111llll2222 /aws/vendedlogs/alb-hc-plain-default plain 10 items, default order
mmmm3333nnnn4444 /aws/vendedlogs/alb-hc-plain-reordered plain 10 items, swapped order
oooo5555pppp6666 /aws/vendedlogs/alb-hc-json-reordered json 10 items, swapped order

The specified orders are as follows.

デフォルト順: type, time, latency, target_addr, target_group_id, status, status_code, reason_code, elb, ip_address
入替順:       status_code, reason_code, status, time, type, latency, target_addr, target_group_id, elb, ip_address

Below are the same records, matched by time.

plain / default order

http 2026-07-27T14:45:17.556303Z 0.00587012 10.0.0.100:80 example-tg PASS 200 - app/example-alb/0123456789abcdef 198.51.100.11

plain / swapped order

200 - PASS 2026-07-27T14:45:17.556303Z http 0.00587012 10.0.0.100:80 example-tg app/example-alb/0123456789abcdef 198.51.100.11

json / swapped order

{"status_code":"200","reason_code":"-","status":"PASS","time":"2026-07-27T14:45:17.556303Z","type":"http","latency":"0.00587012","target_addr":"10.0.0.100:80","target_group_id":"example-tg","elb":"app/example-alb/0123456789abcdef","ip_address":"198.51.100.11"}

In plain, the columns were rearranged in the specified order, and the first column changed from http (type) to 200 (status_code). Since plain has no column names, a parser that reads by column number will have its value mapping broken. If you have an existing parser that reads by column number on the assumption of the official default order, you need to align the RecordFields order with the official documentation or the description from describe-configuration-templates.

In the JSON output I checked this time as well, the keys appeared in the specified order. However, on the consuming side, you should not depend on the order in which keys appear and should reference them by key name. I have omitted the output in json default order.

Summary

CloudWatch Logs integrated logging for ALB could be managed as IaC by combining three CloudFormation resources. It is a viable option when you want to broaden the destinations for monitoring or downstream processing while keeping storage in S3. I also plan to verify separately how to manage the log settings for the ALB CloudWatch Logs integration in a separate stack, and how to manage them across accounts.

Share this article

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