ALB の CloudWatch Logs 統合ログ記録を CloudFormation で設定してみた

ALB の CloudWatch Logs 統合ログ記録を CloudFormation で設定してみた

ALB のアクセスログ・接続ログ・ヘルスチェックログを S3 へ、アクセスログを CloudWatch Logs と Amazon Data Firehose へ配信する設定を CloudFormation で書いてみました。3つの配信先を、ほぼ共通のリソース構成で書き分けられます。テンプレートで `RecordFields` を指定したときの挙動もあわせて確認しています。
2026.07.28

はじめに

2026年7月23日、Application Load Balancer(ALB)のログが CloudWatch Logs の統合ログ記録(Vended Logs)に対応しました。対象はアクセスログ・接続ログ・ヘルスチェックログの3種類です。配信先には S3、CloudWatch Logs、Amazon Data Firehose を選べます。

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/

3本目のリンクは前記事で、レガシー方式(ALB 属性による S3 配信)との違いや CLI での設定手順を紹介しています。

本記事では Delivery Source / Delivery Destination / Delivery の3リソースを CloudFormation で記述しました。S3・CloudWatch Logs・Firehose の3配信先を同時に構成し、RecordFields は全フィールドが必須のため、指定時の注意点もあわせて確認しています。

検証内容

検証環境

項目
リージョン ap-northeast-1
ALB internet-facing、HTTP:80 と HTTPS:443 の2リスナー
オリジン EC2(Amazon Linux 2023、UserData で nginx を導入)
セキュリティグループ ALB は検証実行元の1アドレスのみ許可
ログタイプ ALB_ACCESS_LOGS / ALB_CONNECTION_LOGS / ALB_HEALTH_CHECK_LOGS

HTTPS リスナーは、接続ログに TLS 関連のフィールドを出すために設けました。

ALB 属性によるレガシー方式の S3 配信は、次のとおり3つとも無効のままにしています。本検証のログ出力はすべて統合ログ記録経由です。

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

リソースは基盤テンプレートと配信テンプレートの2つに分けました。

リソース 作成場所
ALB / EC2 / ターゲットグループ / リスナー 基盤テンプレート(本記事では構成の説明のみ)
S3 バケットとバケットポリシー 配信テンプレート
CloudWatch Logs ロググループ 配信テンプレート
Firehose 配信ストリームと実行ロール 配信テンプレート
Delivery Source / Destination / Delivery 配信テンプレート
ACM 証明書(HTTPS リスナー用) 事前作成。テンプレート外でパラメータとして渡す

Firehose 配信ストリームは DirectPut 型とし、後段は同一 S3 バケットの別プレフィックスへ書き込みました。バッファは60秒 / 5MB、GZIP 圧縮、レコード変換なしです。

3つの配信先を CloudFormation で設定する

AWS::Logs::DeliverySource はログの出どころ(ALB とログタイプ)、AWS::Logs::DeliveryDestination は配信先(S3 バケット・ロググループ・Firehose ストリーム)を表し、この2つを AWS::Logs::Delivery で紐付けます。

配信テンプレート全文
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

配信先が3種類あっても、AWS::Logs::DeliveryDestination 自体の書き方はほぼ共通です。今回のテンプレートで配信先ごとに書き分ける主なプロパティは DeliveryDestinationTypeDestinationResourceArn です。また、S3・CloudWatch Logs・Firehose では周辺リソースの設定も異なります。

まず DeliveryDestinationType です。指定する値は、S3 では S3、CloudWatch Logs では CWL、Firehose では FH です。CloudWatchLogsFirehose のような表記は指定しません。

  S3Destination:
    Properties:
      DeliveryDestinationType: S3

  CwlDestination:
    Properties:
      DeliveryDestinationType: CWL

  FirehoseDestination:
    Properties:
      DeliveryDestinationType: FH

次に DestinationResourceArn です。S3 はバケット、CloudWatch Logs はロググループ、Firehose は配信ストリームの ARN を渡します。いずれも同一テンプレート内で作成したリソースを !GetAtt で参照できるため、ARN を文字列で組み立てる必要はありません。

  S3Destination:
    Properties:
      DestinationResourceArn: !GetAtt LogsBucket.Arn

  CwlDestination:
    Properties:
      DestinationResourceArn: !GetAtt AccessLogGroup.Arn

  FirehoseDestination:
    Properties:
      DestinationResourceArn: !GetAtt FirehoseStream.Arn

S3 宛では、バケットポリシーで delivery.logs.amazonaws.coms3:PutObjects3:GetBucketAcl を許可します。呼び出し元は aws:SourceAccountaws: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}:*

CloudWatch Logs 宛では、ロググループに delivery.logs.amazonaws.comlogs:CreateLogStreamlogs:PutLogEvents を許可するリソースポリシーが必要です。今回のテンプレートでは AWS::Logs::ResourcePolicy を明示的に定義していません。設定者に必要な権限があれば、Delivery 作成時に AWS がこのポリシーを自動設定します(Logs sent to CloudWatch Logs)。

なお、Firehose 配信ストリーム自身が後段の S3 へ書き込むための実行ロールは必要で、テンプレートの FirehoseToS3Role として定義しています。

一方で、Firehose 宛に delivery.logs.amazonaws.com を信頼する自作ロールは不要でした。当初テンプレートに用意していたそのロールを削除してスタックを更新した後も、3配信先への配信は継続していました。CloudWatch Logs は Firehose への直接書き込みにサービスリンクロール AWSServiceRoleForLogDelivery を使用します(Using service-linked roles for CloudWatch Logs)。

権限の話とは別に、Delivery Source、Delivery Destination、Delivery の3リソースにはいずれもコスト配分タグを設定できます。CloudWatch Logs 統合のコストを把握する意図で、今回はテンプレート内の全リソースに CmBillingGroup を付与しました。

デプロイ後に aws logs describe-deliveries で確認した配信の一覧が次のとおりです。

Delivery ID Delivery Source Delivery Destination 種別 recordFields
aaaa1111bbbb2222 alb-access-logs alb-logs-s3-destination S3 未指定
cccc3333dddd4444 alb-connection-logs alb-logs-s3-destination S3 未指定
eeee5555ffff6666 alb-health-check-logs alb-logs-s3-destination S3 未指定
gggg7777hhhh8888 alb-access-logs alb-logs-cwl-destination CWL 未指定
iiii9999jjjj0000 alb-access-logs alb-logs-firehose-destination FH 未指定

アクセスログの Delivery Source alb-access-logs は S3・CWL・FH の3宛先に紐付いています。1つの Delivery Source を複数の配信先に紐付け、並行して配信できます。

3つの配信先に届いたログを確認する

ALB に HTTP と HTTPS でリクエストを送り、3つの配信先それぞれに届いたログを確認しました。OutputFormat はすべて json を指定しています。

S3 には次のキーでオブジェクトが作成されました。

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

先頭の AWSLogs/<アカウント ID>/elasticloadbalancing/ はサービス側が付与する部分です。S3SuffixPath{account-id} を含めたため、1つのキーにアカウント ID が2回現れました。重複を避けるなら S3SuffixPath から {account-id} を外すとよいです。

S3 に届いたアクセスログ(HTTPS リクエスト)
{"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"}

CloudWatch Logs では、テンプレートで作成したロググループ /aws/vendedlogs/alb-access-logs にレコードが届いていました。1レコードが1イベントとして格納されます。

CloudWatch Logs に届いたアクセスログ(HTTP リクエスト)
{"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 配信は、ストリームの後段に指定した S3 のオブジェクト(firehose/access-logs/2026/07/27/14/ 配下)で確認しました。レコードのフィールド構成は他の配信先と同じです。

Firehose 経由で S3 に届いたアクセスログ(HTTPS リクエスト)
{"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"}

S3 に届いた接続ログでは、HTTPS リスナー宛(listener_port443)のレコードで tls_protocoltls_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"}

今回、ALB の対象ログタイプについて describe-configuration-templates で確認したところ、配信先タイプごとの OutputFormat に指定できる値は次のとおりでした。

配信先 選択可能なフォーマット
S3 plain / json / w3c / parquet
CloudWatch Logs plain / json
Firehose plain / json / raw

この検証では ALB と EC2、Firehose 配信ストリーム、CloudWatch Logs と Firehose 宛の Vended Logs 配信、S3 と CloudWatch Logs の保管に対して課金が発生し得ます。ALB の統合ログ記録から S3 への配信自体は無料です。確認後はスタックを削除しました。

RecordFields を指定するときの注意点

AWS::Logs::DeliveryRecordFields では出力するフィールドを指定できます。今回 ap-northeast-1 で確認した ALB の3ログタイプでは、全フィールドが必須でした。そのため、この時点の構成では RecordFields は実質的に出力順の制御にしか使えません。ALB の3ログタイプについて aws logs describe-configuration-templates で確認したフィールド定義は次のとおりです。

ログタイプ フィールド数 うち mandatory
ALB_ACCESS_LOGS 34 34
ALB_CONNECTION_LOGS 15 15
ALB_HEALTH_CHECK_LOGS 10 10

いずれのログタイプも全フィールドが mandatory: true でした。この時点で確認した構成では、フィールドの取捨選択はできません。

実際に ALB_HEALTH_CHECK_LOGS の10フィールドのうち timestatus_code の2つだけを指定してデプロイしたところ、スタックイベントの ResourceStatusReason は次のエラーになりました。

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

同じ指定を aws logs create-delivery で実行した場合も同様に失敗しました。

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

RecordFields を書かなければ、サービス既定のフィールドセットと順序で出力されます。今回確認した構成ではフィールドの取捨選択はできないため、テンプレート上の選択肢は「全フィールドを列挙して順序を制御する」か「RecordFields プロパティ自体を書かない」の2つです。

残る用途は出力順の制御です。掲載したテンプレートとは別に、OutputFormatplain の Destination と RecordFields を列挙した Delivery を追加し、ALB_HEALTH_CHECK_LOGS の10フィールドを公式の既定順と入替順でそれぞれ配信しました。

Delivery ID 配信先ロググループ フォーマット RecordFields
kkkk1111llll2222 /aws/vendedlogs/alb-hc-plain-default plain 10項目・デフォルト順
mmmm3333nnnn4444 /aws/vendedlogs/alb-hc-plain-reordered plain 10項目・入替順
oooo5555pppp6666 /aws/vendedlogs/alb-hc-json-reordered json 10項目・入替順

指定した順序は次のとおりです。

デフォルト順: 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

以下は time が一致する同一レコードです。

plain / デフォルト順

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 / 入替順

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 / 入替順

{"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"}

plain では指定した順序どおりに列が並び替わり、1列目が httptype)から 200status_code)に変わりました。plain は列名を持たないため、列番号で読むパーサーがあると値の対応が崩れます。公式の既定順を前提に列番号で読む既存のパーサーがある場合は、RecordFields の順序を公式ドキュメントや describe-configuration-templates の記述に揃える必要があります。

今回確認した JSON 出力でも、キーは指定順に出現しました。ただし、利用側ではキーの出現順に依存せず、キー名で参照します。json の既定順の出力は掲載を省略しています。

まとめ

ALB の CloudWatch Logs 統合ログ記録は、CloudFormation の3リソースを組み合わせて IaC 管理できました。S3 への保管を維持しつつ、監視や後段処理の用途で配信先を広げたい場面で選択肢に入ります。ALB の CloudWatch Logs 統合のログ設定を別スタックで管理する方法や、クロスアカウントで管理する方法についても、別途検証する予定です。

この記事をシェアする

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

関連記事