[新機能] CloudWatch Logs Insights のクエリ結果を動的な Lookup Table にしてみた
はじめに
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 コマンド(CloudWatch Logs Insights クエリ構文)
- create-lookup-table(AWS CLI コマンドリファレンス)
- Automating log analysis with scheduled queries
クエリ結果からの作成、スケジュールクエリでの自動更新、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-results で status が Complete になったことを確認してから次に進みます。
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"
}
}'
state は ENABLED で返りました。
{
"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:StartQuery・logs:GetQueryResults)と Lookup Table の更新(logs:UpdateLookupTable)を許可しています。
設定から約10時間後に get-scheduled-query-history で実行履歴を取得すると、10件が記録されており、すべて executionStatus が Complete でした。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
結果は次のとおりです(抜粋。userArn は arn: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 をキーに、userArn・callerIp・operationCount の3列が付与されました。いずれも元のログイベントには含まれないフィールドです。今回エンリッチしたのは CloudTrail の SSM 操作ログです。調査時に eventName だけ分かっている状況で、呼び出し元の当たりを付けるのに使えます。
ただし、この userArn と callerIp は元のクエリで latest() を使って集計した eventName 単位の代表値です。同じ eventName に複数の呼び出し元がある場合、どの行にも最後の1件の値が付きます。個々のイベントの呼び出し元を特定する用途には使えません。
まとめ
クエリ結果から作った Lookup Table をスケジュールクエリと組み合わせれば、エンリッチ用の参照データを最新に保つ運用自体を CloudWatch Logs に任せられます。ログの中にある情報でログを補完したい場面では、queryId 方式が、CSV を作り直して再アップロードする運用を置き換える選択肢になります。








