[新機能] CloudWatch Logs Insights のクエリ結果を動的な Lookup Table にしてみた

[新機能] CloudWatch Logs Insights のクエリ結果を動的な Lookup Table にしてみた

CloudWatch Logs Insights の Lookup Table を、クエリ結果(queryId)から直接作成できるようになりました。CSV を用意せずに、ログデータ自体を参照テーブルにできます。CloudTrail の SSM 操作ログを例に、スケジュールクエリでの自動更新と `lookup` での参照まで AWS CLI で試しました。
2026.08.03

はじめに

2026年7月31日(UTC)、AWS CLI 2.36.14 で、CloudWatch Logs Insights の Lookup Table として、クエリ結果が利用可能になるアップデートがありました。2.36.14 の変更内容から、logs の項目だけを取り出します。

curl -s https://raw.githubusercontent.com/aws/aws-cli/2.36.14/.changes/2.36.14.json \
  | jq '.[] | select(.category == "``logs``")'
{
  "category": "``logs``",
  "description": "Amazon CloudWatch Logs now lets you create and update lookup tables directly from CloudWatch Logs query results by passing a queryId, and configure a lookup table as a scheduled query destination so it refreshes automatically with the latest query results on each run.",
  "type": "api-change"
}

CloudWatch Logs Insights の Lookup Table は、CSV 形式の参照データをクエリ結果に結合して補完(エンリッチ)するための機能です。クエリの中で lookup コマンドを使うと、ログイベントのフィールドをキーにテーブルの行を引き当て、指定した列を出力に追加できます。

従来 Lookup Table は、外部で用意した CSV をアップロードして作成する方式のみでしたが、今回、クエリ結果(queryId)から直接作成する方式が加わりました。さらに、スケジュールクエリの出力先に Lookup Table を指定すれば、テーブルを自動更新できます。

クエリ結果からの作成、スケジュールクエリでの自動更新、lookup での参照を、AWS CLI で順に試します。

固定 CSV 方式との比較

違いは、テーブルの中身をどこから持ってくるかです。固定 CSV 方式は、外部で用意したファイルをアップロードしてテーブルを作り、内容を変えたいときは CSV を作り直して再アップロードします。queryId 方式は、実行したクエリの結果がそのままテーブルの中身になります。さらにスケジュールクエリの出力先に指定すれば、CloudWatch Logs 側で定期的に上書きされ、間隔は最短1分まで指定できます。

queryId 方式なら、外部ツールやスクリプトを挟まず、CloudWatch Logs だけでログデータを自己参照してエンリッチできます。社員マスタや IP アドレス管理台帳のように外部システムのデータを持ち込む場合は CSV 方式が、ログ内のデータで完結する場合は queryId 方式が適しています。

検証環境

  • リージョン:ap-northeast-1
  • CloudTrail のログ出力先:SSM 操作ログを /aws/cloudtrail/lookup-lab ロググループへ
  • 操作ログの発生源:EC2(Amazon Linux 2023 / t3.micro)に SSM 用のロールを付与し、SSM Run Command を実行

クエリを実行して queryId を取得する

Lookup Table の元データとなるクエリを実行します。ここでは CloudTrail ログから、eventName ごとの操作者 ARN・送信元 IP・件数を集計します。

aws logs start-query \
  --region ap-northeast-1 \
  --log-group-name /aws/cloudtrail/lookup-lab \
  --start-time <開始時刻(epoch秒)> \
  --end-time <終了時刻(epoch秒)> \
  --query-string '
fields userIdentity.arn, sourceIPAddress, eventName, eventSource
| filter eventSource = "ssm.amazonaws.com"
| filter ispresent(userIdentity.arn)
| stats count(*) as operationCount,
        latest(userIdentity.arn) as userArn,
        latest(sourceIPAddress) as callerIp
  by eventName'

返ってくるのは queryId だけです。

{
    "queryId": "0d00be57-b126-471f-8bd2-c7d692167e93"
}

start-query はクエリを非同期で開始します。get-query-resultsstatusComplete になったことを確認してから次に進みます。

queryId から Lookup Table を作成する

create-lookup-table コマンドに --query-id を渡すと、そのクエリ結果をそのまま Lookup Table として保存できます。

aws logs create-lookup-table \
  --region ap-northeast-1 \
  --lookup-table-name ssm_operations_by_user \
  --query-id 0d00be57-b126-471f-8bd2-c7d692167e93 \
  --description "SSM operations grouped by eventName with caller info" \
  --tags Project=lookup-table-lab

レスポンスにはテーブルの ARN と作成時刻が含まれます。

{
    "lookupTableArn": "arn:aws:logs:ap-northeast-1:123456789012:lookup-table:ssm_operations_by_user",
    "createdAt": 1785715999458
}

get-lookup-table で内容を確認します。

aws logs get-lookup-table \
  --region ap-northeast-1 \
  --lookup-table-name ssm_operations_by_user

内容は tableBody に CSV 形式で入っています。

eventName,operationCount,userArn,callerIp
SendCommand,1,arn:aws:sts::123456789012:assumed-role/my-role/my-session,203.0.113.10
ListInstanceAssociations,2,arn:aws:sts::123456789012:assumed-role/ec2-role/i-0abcdef1234567890,203.0.113.20
RegisterManagedInstance,1,arn:aws:sts::123456789012:assumed-role/aws:ec2-instance/i-0abcdef1234567890,203.0.113.20
UpdateInstanceInformation,3,arn:aws:sts::123456789012:assumed-role/ec2-role/i-0abcdef1234567890,203.0.113.20
DescribeInstanceInformation,3,arn:aws:sts::123456789012:assumed-role/my-role/my-session,203.0.113.10

列名は stats ... by eventName の出力と同じで、eventName が、後述する lookup での結合キーになります。

スケジュールクエリの出力先に Lookup Table を設定する

作成したテーブルの内容は、作成時のクエリ結果のまま固定されます。手動で更新する場合は update-lookup-table でテーブル全体を置き換えます。

スケジュールクエリの結果は通常 S3 などに保存します。出力先(--destination-configuration)に lookupTableConfiguration を指定すると、Lookup Table を自動更新できます。

aws logs create-scheduled-query \
  --region ap-northeast-1 \
  --name ssm-operations-lookup-refresh \
  --query-language CWLI \
  --query-string '(前述のクエリと同じ)' \
  --log-group-identifiers "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/cloudtrail/lookup-lab" \
  --schedule-expression "cron(0 * * * ? *)" \
  --start-time-offset 3600 \
  --end-time-offset 0 \
  --execution-role-arn "arn:aws:iam::123456789012:role/scheduled-query-role" \
  --destination-configuration '{
    "lookupTableConfiguration": {
      "tableName": "ssm_operations_by_user",
      "roleArn": "arn:aws:iam::123456789012:role/scheduled-query-role"
    }
  }'

stateENABLED で返りました。

{
    "scheduledQueryArn": "arn:aws:logs:ap-northeast-1:123456789012:scheduled-query:4c77af92-6c6b-4894-b06f-43927d518ce8",
    "state": "ENABLED"
}

cron(0 * * * ? *) の指定により、1時間ごとに実行されます。--start-time-offset--end-time-offset は実行時刻からのオフセット(単位は秒)で、ここでは各実行が直近1時間分のログを対象にします。--execution-role-arn に渡すロールは logs.amazonaws.com を信頼するロールです。クエリ実行(logs:StartQuerylogs:GetQueryResults)と Lookup Table の更新(logs:UpdateLookupTable)を許可しています。

設定から約10時間後に get-scheduled-query-history で実行履歴を取得すると、10件が記録されており、すべて executionStatusComplete でした。describe-lookup-tables で見ると、テーブルの lastUpdatedTime もトリガー時刻の約1分後に更新されていました。履歴の1件を抜粋します。

{
    "executionStatus": "Complete",
    "destinations": [
        {
            "destinationType": "LOOKUP_TABLE",
            "destinationIdentifier": "ssm_operations_by_user",
            "status": "COMPLETE"
        }
    ]
}

--schedule-expression は分単位の精度に対応しており、cron(* * * * ? *) を指定したスケジュールクエリも登録できました。実行履歴には1分刻みの実行が記録されました。

lookup コマンドで Lookup Table を参照する

lookup コマンドを使うと、クエリの中で Lookup Table のデータを結合できます。構文は lookup <テーブル名> <結合キー> OUTPUT <取得フィールド> です。結合キーはテーブルの列名で指定し、ログイベント側のフィールド名が異なる場合は 列名 as フィールド名 と書きます。

CloudTrail ログの SSM イベントに、ssm_operations_by_user テーブルの操作者 ARN・送信元 IP・操作件数を付与してみます。

fields eventName, @timestamp
| filter eventSource = "ssm.amazonaws.com"
| lookup ssm_operations_by_user eventName OUTPUT userArn, callerIp, operationCount
| fields eventName, @timestamp, userArn, callerIp, operationCount
| filter ispresent(userArn)
| sort @timestamp desc
| limit 10

結果は次のとおりです(抜粋。userArnarn:aws:sts::123456789012: を省略しています)。

eventName @timestamp userArn callerIp operationCount
UpdateInstanceInformation 2026-08-03 00:16:26 assumed-role/ec2-role/i-0abcdef... 203.0.113.20 3
DescribeInstanceInformation 2026-08-03 00:13:11 assumed-role/my-role/my-session 203.0.113.10 3
ListInstanceAssociations 2026-08-03 00:12:06 assumed-role/ec2-role/i-0abcdef... 203.0.113.20 2
SendCommand 2026-08-03 00:12:06 assumed-role/my-role/my-session 203.0.113.10 1
RegisterManagedInstance 2026-08-03 00:12:06 assumed-role/aws:ec2-instance/i-0abcdef... 203.0.113.20 1

eventName をキーに、userArncallerIpoperationCount の3列が付与されました。いずれも元のログイベントには含まれないフィールドです。今回エンリッチしたのは CloudTrail の SSM 操作ログです。調査時に eventName だけ分かっている状況で、呼び出し元の当たりを付けるのに使えます。

ただし、この userArncallerIp は元のクエリで latest() を使って集計した eventName 単位の代表値です。同じ eventName に複数の呼び出し元がある場合、どの行にも最後の1件の値が付きます。個々のイベントの呼び出し元を特定する用途には使えません。

まとめ

クエリ結果から作った Lookup Table をスケジュールクエリと組み合わせれば、エンリッチ用の参照データを最新に保つ運用自体を CloudWatch Logs に任せられます。ログの中にある情報でログを補完したい場面では、queryId 方式が、CSV を作り直して再アップロードする運用を置き換える選択肢になります。

この記事をシェアする

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

関連記事