I tried setting up CloudWatch Logs integrated logging for ALB with CloudFormation
This page has been translated by machine translation. View original
Introduction
On July 23, 2026, Application Load Balancer (ALB) logs became compatible with CloudWatch Logs integrated logging (Vended Logs). The three supported log types are access logs, connection logs, and health check logs. You can choose from S3, CloudWatch Logs, and Amazon Data Firehose as delivery destinations.
The third link is the previous article, which introduces the differences from the legacy method (S3 delivery via ALB attributes) and the configuration procedure using CLI.
In this article, the three resources — Delivery Source / Delivery Destination / Delivery — are described in CloudFormation. All three delivery destinations (S3, CloudWatch Logs, and Firehose) are configured simultaneously, and since all fields are required for RecordFields, notes on specifying them are also covered.
Verification Details
Verification Environment
| Item | Value |
|---|---|
| Region | ap-northeast-1 |
| ALB | internet-facing, 2 listeners: HTTP:80 and HTTPS:443 |
| Origin | EC2 (Amazon Linux 2023, nginx installed via UserData) |
| Security Group | ALB allows only 1 address from the verification source |
| Log Types | ALB_ACCESS_LOGS / ALB_CONNECTION_LOGS / ALB_HEALTH_CHECK_LOGS |
The HTTPS listener was set up to output TLS-related fields in the connection logs.
The legacy S3 delivery via ALB attributes is left disabled for all three, 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
Resources were divided into two templates: a foundation template and a delivery template.
| Resource | Where Created |
|---|---|
| ALB / EC2 / Target Group / Listener | Foundation template (this article only describes the configuration) |
| 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 HTTPS listener) | Pre-created. Passed as a parameter outside the template |
The Firehose delivery stream is of type DirectPut, and the backend writes to a different prefix in the same S3 bucket. Buffer is 60 seconds / 5MB, GZIP compression, no record transformation.
Configuring Three Delivery Destinations in CloudFormation
AWS::Logs::DeliverySource represents the log origin (ALB and log type), AWS::Logs::DeliveryDestination represents the delivery destination (S3 bucket, log group, or Firehose stream), and these two are linked via 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 types of delivery destinations, the way AWS::Logs::DeliveryDestination itself is written is mostly the same. The main properties that differ per destination in this template are DeliveryDestinationType and DestinationResourceArn. Additionally, the surrounding resource configurations differ for S3, CloudWatch Logs, and Firehose.
First, DeliveryDestinationType. The values to specify are S3 for S3, CWL for CloudWatch Logs, and FH for Firehose. Notations such as CloudWatchLogs or Firehose are not used.
S3Destination:
Properties:
DeliveryDestinationType: S3
CwlDestination:
Properties:
DeliveryDestinationType: CWL
FirehoseDestination:
Properties:
DeliveryDestinationType: FH
Next, DestinationResourceArn. Pass the ARN of the S3 bucket, CloudWatch Logs log group, or Firehose delivery stream respectively. Since all are resources created within the same template, they can be referenced using !GetAtt, so there is no need to construct 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 grants delivery.logs.amazonaws.com permission for s3:PutObject and s3:GetBucketAcl. The caller is restricted using 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, a resource policy is required on the log group that grants delivery.logs.amazonaws.com permission for logs:CreateLogStream and logs:PutLogEvents. In this template, AWS::Logs::ResourcePolicy is not explicitly defined. If the person configuring it has the necessary permissions, AWS automatically sets this policy when creating the Delivery (Logs sent to CloudWatch Logs).
Note that an execution role for the Firehose delivery stream itself to write to the backend S3 is required, and it is defined as FirehoseToS3Role in the template.
On the other hand, a custom role trusting delivery.logs.amazonaws.com for Firehose destinations was not needed. After removing that role from the template and updating 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).
Apart from the permissions topic, cost allocation tags can be set on all three resources: Delivery Source, Delivery Destination, and Delivery. With the intent of tracking CloudWatch Logs integration costs, CmBillingGroup was assigned to all resources in the template.
The list of deliveries confirmed 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 all three destinations: S3, CWL, and FH. A single Delivery Source can be linked to multiple delivery destinations and delivered in parallel.
Verifying Logs Delivered to the Three Destinations
Requests were sent to the ALB via HTTP and HTTPS, and the logs delivered to each of the three destinations were verified. OutputFormat is set to json for all.
In S3, objects were 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 AWSLogs/<account ID>/elasticloadbalancing/ prefix at the beginning is added by the service side. Since {account-id} was included in S3SuffixPath, the account ID appears twice in a single key. To avoid duplication, remove {account-id} from S3SuffixPath.
Access log delivered to 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 were delivered to the log group /aws/vendedlogs/alb-access-logs created in the template. Each record is stored as one event.
Access log delivered to 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"}
Firehose delivery was confirmed in S3 objects (under firehose/access-logs/2026/07/27/14/) specified as the backend of the stream. The field structure of the records is the same as for other delivery destinations.
Access log delivered to 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 delivered to S3, records for HTTPS listener destinations (where listener_port is 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"}
In this verification, when the target log types for ALB were checked with describe-configuration-templates, the available values for OutputFormat per delivery destination type were as follows.
| Destination | Available 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. The delivery itself from ALB integrated logging to S3 is free. After verification, the stack was deleted.
Notes on Specifying RecordFields
In AWS::Logs::Delivery, RecordFields allows you to specify which fields to output. For the three ALB log types verified in ap-northeast-1 this time, all fields were mandatory. Therefore, in the configuration at this point, RecordFields can only practically be used to control output order. The field definitions confirmed via 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 in every log type were mandatory: true. In the configuration verified at this point, selecting or excluding fields is not possible.
When actually deploying with only 2 of the 10 ALB_HEALTH_CHECK_LOGS fields specified — time and status_code — the ResourceStatusReason in the stack events showed 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 RecordFields is not written, the output will use the service's default field set and order. Since field selection is not possible in the configuration verified this time, the options in the template are either "enumerate all fields to control the order" or "omit the RecordFields property entirely."
The remaining use case is controlling output order. Separately from the template shown, a Destination with OutputFormat set to plain and a Delivery with RecordFields enumerated were added, delivering the 10 fields of ALB_HEALTH_CHECK_LOGS in the official default order and in a reordered arrangement respectively.
| 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, reordered |
| oooo5555pppp6666 | /aws/vendedlogs/alb-hc-json-reordered | json | 10 items, reordered |
The specified orders are as follows.
Default order: type, time, latency, target_addr, target_group_id, status, status_code, reason_code, elb, ip_address
Reordered: status_code, reason_code, status, time, type, latency, target_addr, target_group_id, elb, ip_address
The following are records from the same entry with a matching 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 / reordered
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 / reordered
{"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 format, the columns are rearranged in the specified order, and the first column changed from http (type) to 200 (status_code). Since plain format has no column names, parsers that read by column number will have mismatched value mappings. If existing parsers read by column number based on the official default order, the order in RecordFields must be aligned with the official documentation or the definitions in describe-configuration-templates.
In the JSON output verified this time as well, keys appeared in the specified order. However, on the consumer side, keys should be referenced by key name rather than by order of appearance. The JSON output in default order is omitted from this post.
Summary
ALB's CloudWatch Logs integrated logging can be managed as IaC by combining three CloudFormation resources. It is an option worth considering when you want to expand delivery destinations for monitoring or downstream processing purposes while maintaining storage to S3. I also plan to separately verify methods for managing ALB CloudWatch Logs integration log settings in a separate stack, as well as cross-account management approaches.
