CloudWatch pipelines の新機能「条件付きログルーティング」を試してみた

CloudWatch pipelines の新機能「条件付きログルーティング」を試してみた

Amazon CloudWatch pipelines の新機能「条件付きログルーティング」で、JSON ログを level や type の条件に応じて別のロググループへ振り分けました。あわせて、JSON をパースした後に grok でフィールドを抽出する連鎖パーサーの動作も確認しました。
2026.10.15

はじめに

2026 年 10 月 14 日、CloudWatch pipelines が条件付きログルーティングと連鎖パーサーに対応しました。

https://aws.amazon.com/jp/about-aws/whats-new/2026/10/cloudwatch-pipelines-log-routing/

条件付きルーティングは、パイプラインで抽出・加工したフィールドの条件に応じて、ログイベントの送信先ロググループを振り分ける機能です。送信先ごとにロググループを分けることで、保持期間やアクセス制御を用途に合わせて設定できます。たとえば、1 つのロググループに届く JSON ログから、後段の ETL や分析の対象から外すイベントを別のロググループへ分離できます。

この記事では、東京リージョンで、ダミーの JSON ログを level と type の条件で 2 つのロググループへ振り分け、次の 3 点を確かめました。

  • どの条件のイベントがどのロググループに届くか
  • 振り分けたイベントが元のロググループに残るか
  • 振り分け先でタイムスタンプがどうなるか

あわせて、JSON をパースした後に grok でフィールドを抽出する、連鎖パーサーの動作も確認しました。

検証の構成

題材は Lambda Web Functions のプラットフォームログに似せた JSON で、1 件目は次の形です。

{"level":"WARN","type":"platform.invokeReport","seq":1,"region":"ap-northeast-1","record":{"requestId":"req-001","durationMs":11}}

ロググループは /test/pipelines-routing-1015/ の下に作り、以下では末尾の名前で source(送信元)、warn、summary と書きます。

パイプラインには、プロセッサーとして parse_json を 1 つ、シンクとして 3 つを設定しました。level が WARN のイベントは warn に、type が platform.invokeSummary のイベントは summary に送ります。どちらの条件にも一致しないイベントは、デフォルトルートとして元のロググループ(@original)に送ります。設定は次の YAML のとおりです。<ACCOUNT_ID> は、自分の 12 桁の AWS アカウント ID に置き換えてください。

pipeline:
  source:
    cloudwatch_logs:
      aws:
        sts_role_arn: "arn:aws:iam::<ACCOUNT_ID>:role/pipelines-routing-1015-role"
      log_event_metadata:
        data_source_name: "routing1015"
        data_source_type: "default"
  processor:
    - parse_json: {}
  sink:
    - cloudwatch_logs:
        log_group: "/test/pipelines-routing-1015/warn"
        when: 'level == "WARN"'
    - cloudwatch_logs:
        log_group: "/test/pipelines-routing-1015/summary"
        when: 'type == "platform.invokeSummary"'
    - cloudwatch_logs:
        log_group: "@original"
        is_default: true

条件は、シンクを YAML に記述した順に評価されます。複数の条件に一致するイベントは、最初に一致したシンクへ送られます。

シンクが複数あるパイプラインでは、デフォルトルートを 1 つだけ指定し、最後に記述します。1 つのパイプラインに設定できるシンクは、デフォルトルートを含めて最大 10 個です。条件式は 4,096 文字まで指定できます。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/pipeline-sinks.html

送信元ロググループとパイプラインは、データソースタグで関連付けます。この例では、送信元の source に cw:datasource:name と cw:datasource:type のタグを設定します。

各タグの値は、YAML の log_event_metadata にある対応する値と一致させます。つまり、cw:datasource:name は data_source_name に、cw:datasource:type は data_source_type に対応します。両方の値が一致するロググループのイベントが、そのパイプラインの入力になります。

aws logs tag-resource --resource-arn arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/test/pipelines-routing-1015/source --tags cw:datasource:name=routing1015,cw:datasource:type=default

--configuration には、YAML 全体を文字列にして Body に入れた JSON ファイルを渡します。作成前に validate-telemetry-pipeline-configuration で検証できます。

jq -Rs '{Body: .}' routing-pipeline.yaml > routing-config.json
aws observabilityadmin validate-telemetry-pipeline-configuration --configuration file://routing-config.json
aws observabilityadmin create-telemetry-pipeline --name routing-1015-test --configuration file://routing-config.json
ソースロールの信頼ポリシーと権限ポリシー

本番では、信頼ポリシーに aws:SourceAccount や aws:SourceArn の条件を付け、権限ポリシーのリソースも対象の ARN に制限することを推奨します。この記事では、これらの条件や ARN を制限した構成での動作は確認していません。

信頼ポリシー(trust-policy.json)は、CloudWatch Logs にロールの引き受けを許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "logs.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

権限ポリシー(source-role-policy.json)は、送信元のロググループへの ProcessWithPipeline と、振り分け先の作成・書き込みを許可します。送信元は、振り分けの検証用の source と、後述する連鎖パーサーの検証用の source-chain の 2 つです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LogsProcessing",
      "Effect": "Allow",
      "Action": "logs:ProcessWithPipeline",
      "Resource": [
        "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/test/pipelines-routing-1015/source",
        "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/test/pipelines-routing-1015/source-chain"
      ]
    },
    {
      "Sid": "CreateRoutedLogGroups",
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "*"
    },
    {
      "Sid": "WriteRoutedLogEvents",
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/test/pipelines-routing-1015/*:*"
    }
  ]
}

条件ごとの振り分け結果

17 件のダミーイベントを 1 回の put-log-events で送り、入力のパターンごとに振り分け先を数えました。

入力イベント 件数 振り分け先
level が WARN、type が platform.invokeReport 5 warn
level が INFO、type が platform.invokeSummary 4 summary
level が INFO、type が platform.invokeReport 3 元のロググループ
level が WARN、type が platform.invokeSummary(両方の条件に合致) 2 warn
level なし 1 元のロググループ
level が小文字の warn 1 元のロググループ
JSON ではない文字列 1 元のロググループ

振り分け先ごとの合計は warn 7 件、summary 4 件、元のロググループ 6 件で、入力の 17 件と一致しました。振り分けたイベントは元のロググループに残りません。

両方の条件に合うイベントは、YAML で先に書いた warn のルートに届きました。小文字の warn のイベントは level == "WARN" の条件に合致せず、元のロググループに届きました。

parse_json だけの今回の設定では、warn と summary に届いた本文は入力と同じ文字列でした。

振り分け先のタイムスタンプ

WARN と INFO を交互に計 4 件、それぞれ 1 時間前、30 分前、10 分前、現在のタイムスタンプを付けて送りました。時刻は JST です。

送ったイベント 元のタイムスタンプ 振り分け先 保存されたタイムスタンプ
WARN(seq 100)、1 時間前 10:08:55.756 warn 11:08:56.805
INFO(seq 101)、30 分前 10:38:55.757 元のロググループ 10:38:55.757
WARN(seq 102)、10 分前 10:58:55.758 warn 11:08:56.805
INFO(seq 103)、現在 11:08:55.759 元のロググループ 11:08:55.759

warn に届いた 2 件は、元の時刻が 1 時間前でも 10 分前でも同じタイムスタンプになりました。この値は取り込み時刻(ingestionTime)の約 0.2 秒前で、パイプラインが処理した時点の時刻と見られます。前節で一括送信した 17 件は、送信時のタイムスタンプが 1 ミリ秒ずつ異なっていましたが、振り分け先に届いた 11 件はすべて同じタイムスタンプでした。

振り分け先のログを時刻で集計したりクエリしたりするなら、送る側でログの本文に時刻フィールドを含めておく必要があります。

不要なログの扱い

CloudWatch Logs をソースにするパイプラインでは、drop_events でイベントを破棄できませんでした。不要なログを後段の ETL や分析の対象から外すには、保持期間を短く設定した別のロググループへ振り分けます。

cloudwatch_logs ソースに drop_events を設定すると、validate-telemetry-pipeline-configuration で PROCESSOR_SOURCE_INCOMPATIBLE となり、create-telemetry-pipeline も ValidationException で失敗しました。今回は、保持期間を 1 日に設定した summary を事前に作成し、level が INFO の platform.invokeSummary イベント 4 件を振り分けました。

保持期間は、振り分け先ロググループの設定に従います。条件ルートの振り分け先ロググループは、存在しなければ初めてイベントを送る際に自動作成されます。今回、自動作成された warn には保持期間が設定されず、ログが無期限に保持される状態でした。保持期間を管理したい振り分け先は、パイプラインの作成前にロググループを作成しておくことを推奨します。

一方、デフォルトルートの振り分け先は自動作成されません。パイプラインを作成する前に、あらかじめ作成しておく必要があります。

連鎖パーサー

連鎖パーサーでは、parse_json の後に grok を配置し、JSON の message フィールドを解析する構成を試しました。送信元には別のロググループ source-chain を用意し、前述と同じ手順でデータソースタグを付けました。タグの値は、YAML の data_source_name に対応する chain1015 と、data_source_type に対応する default です。設定は次の YAML です。

pipeline:
  source:
    cloudwatch_logs:
      aws:
        sts_role_arn: "arn:aws:iam::<ACCOUNT_ID>:role/pipelines-routing-1015-role"
      log_event_metadata:
        data_source_name: "chain1015"
        data_source_type: "default"
  processor:
    - parse_json: {}
    - grok:
        match:
          message: ["%{WORD:method} %{URIPATH:path} %{NUMBER:status} %{NUMBER:duration_ms}ms"]
  sink:
    - cloudwatch_logs:
        log_group: "@original"

シンクが 1 つだけのこの設定では、is_default を付けなくても検証を通り、動作しました。

message に文字列を持つ JSON を 3 件送った結果です。

入力の message 結果
GET /api/items 200 12ms method が GET、path が /api/items、status が "200"、duration_ms が "12"
POST /api/orders 500 340ms method が POST、path が /api/orders、status が "500"、duration_ms が "340"
not matching text フィールドは追加されず、そのまま届いた

grok が抽出した値はすべて文字列でした。パターンに一致したイベントの本文は、追加フィールドを含む JSON に書き換えられて保存されました。これは、parse_json だけの振り分け設定で本文が変わらなかった挙動とは異なります。

プロセッサーの配置や使用数の制限など、詳細は次のドキュメントを参照してください。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/parser-processors.html

料金

CloudWatch pipelines 自体に追加料金はありませんが、通常の CloudWatch Logs の取り込み料金がかかります。CloudWatch Logs をソースにする場合、取り込み料金はパイプラインで処理する前に発生します。そのため、短期保持のロググループへ振り分けて後段の処理対象から外したログにも、取り込み料金はかかります。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-pipelines.html

まとめ

CloudWatch pipelines の条件付きログルーティングを使うと、ログの内容に応じて取り込み時に別のロググループへ振り分けられます。保持期間やアクセス制御が異なるログを分けられるほか、後段の ETL や CloudWatch Logs Insights で対象ロググループを絞ることで、処理対象やクエリのスキャン量を抑えられます。

CloudWatch pipelines 自体に追加料金はありませんが、取り込み料金は振り分け前に発生します。短期保持で抑えられるのはストレージコストです。

振り分け先では元イベントのタイムスタンプが保持されないため、元の時刻で集計・検索する必要がある場合は、ログ本文にも時刻フィールドを含める必要がある点にご留意ください。

この記事をシェアする

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

関連記事