I Tried Configuring ALB's CloudWatch Logs Integrated Logging with CloudFormation
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.
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.
