
Kiroの「スキル」でCloudWatch Logs Insightsの新コマンド・関数を使ったクエリ生成を改善してみた
はじめに
2026年5月と6月に、CloudWatch Logs Insightsのクエリ言語(Logs Insights QL)へ計36個の新コマンド・関数・構文拡張が追加されました。
本記事では、これらの新機能を使ったクエリ生成をAIコーディングエージェントに依頼し、Kiro CLIの「スキル」機能の有無で出力を比較しました。
スキルなし条件(エージェント未指定)では、新コマンド・関数の存在を否定したり、正規表現による代替クエリを生成したりするケースがありました。一方、スキルあり条件(エージェント指定)では、スキル本体・エージェントプロンプト・構文リファレンスを参照し、対象の新構文を使ったクエリ出力が得られました。
なお、スキルなし条件の出力にはモデルの知識カットオフの影響も考えられます。ただし本記事では原因を断定せず、同一入力プロンプトに対する出力差の比較として扱います。
スキルの作成プロセスは以下の記事で紹介しています。
スキルの概要
今回使用したスキルは、スキル本体(行動ルール)・ナレッジベース(構文リファレンス)・エージェント定義の3ファイル構成です。スキルの設計思想や作成プロセスの詳細は先行記事を参照してください。
主なポイントは以下のとおりです。
- スキル本体: Critical Rules(11個)で
limit必須、ispresent()付与、now() * 1000とtoMillis()の単位統一などを指示 - ナレッジベース: 2026年5〜6月追加の新コマンド・関数を含む3言語の参照用リファレンス
- エージェント定義:
resourcesで上記2ファイルを読み込み、--agent cw-logs-queryで起動
検証内容
同一の入力プロンプトに対してスキルなし/ありの2条件でクエリを生成させ、出力を比較しました。テスト2(IP分類)については、スキルあり条件で使用されたIP分類関数を簡略化クエリで実行し、関数の戻り値と分類結果の正確性を確認しています。
検証環境・条件
- モデル:
claude-sonnet-4.6(--modelオプションで条件統一) - 実行方法: Kiro CLIヘッドレスモード(
kiro-cli chat --no-interactive --trust-all-tools) - スキルなし:
/tmpで実行。スキルファイル・ナレッジベース・エージェント定義なし。新規セッション - スキルあり: スキルリポジトリ内で
--agent cw-logs-query指定。新規セッション - 比較観点: 新コマンド・関数の使用有無、公式構文との整合性、出力の簡潔さ
実行コマンドの例です。
# スキルなし
kiro-cli chat --no-interactive --trust-all-tools --model claude-sonnet-4.6 --wrap never \
"プロンプト"
# スキルあり
kiro-cli chat --no-interactive --trust-all-tools --model claude-sonnet-4.6 --wrap never \
--agent cw-logs-query "プロンプト"
テストケース一覧
| # | テーマ | 何を見るか |
|---|---|---|
| 1 | 時刻指定(now() + toMillis()) |
now() の存在を認識するか |
| 1b | CLIスクリプト(aws logs start-query) |
クエリ内での動的時刻指定ができるか |
| 2 | IP分類(isPrivateIP / isPublicIP / isReservedIP) |
2026年6月追加の新関数を使えるか |
| 3 | histogram |
2026年6月追加の新関数を正しく使えるか |
| 4 | parse ... csv |
2026年6月追加の構文拡張を使えるか |
| 5 | parse ... logfmt |
2026年5月追加の構文拡張を使えるか |
テスト1: 時刻指定(now + toMillis)
プロンプト:
CloudWatch Logs Insightsで直近2時間のERRORログを検索するクエリを書いてください。クエリ内で相対時間を指定してください。
スキルなし:
CloudWatch Logs Insights に now() 関数は**ありません**。
now() の存在を否定し、ISO文字列比較やハードコード値を提案しました。
スキルあり:
fields @timestamp, @message
| filter toMillis(@timestamp) >= (now() * 1000 - 7200000)
| filter @message like /(?i)error/
| sort @timestamp desc
| limit 100
now()(エポック秒)× 1000と toMillis(@timestamp)(ミリ秒)を正しく組み合わせたクエリが生成されました。
テスト1b: CLI スクリプト
プロンプト:
aws logs start-query CLIを使って、直近1時間のERRORログを検索するスクリプトを書いてください。クエリ内でも時間を絞り込んでください。
スキルなし:
--query-string 'filter @timestamp >= fromMillis('"$((START * 1000))"') and @timestamp <= fromMillis('"$((END * 1000))"') | filter @message like /ERROR/ | sort @timestamp desc | limit 100'
fromMillis() でシェル変数をクエリ文字列に直接展開する方式でした。now() / toMillis() は未使用です。now() を使えばクエリ内の時間条件を動的に書けるため、クエリ文字列へシェル変数で時刻を埋め込む必要がありません。
スキルあり:
QUERY='fields @timestamp, @message
| filter @message like /(?i)error/
| filter toMillis(@timestamp) >= (now() * 1000 - 3600000)
| sort @timestamp desc
| limit 100'
toMillis(@timestamp) >= (now() * 1000 - 3600000) でクエリ内動的時刻指定を実現しています。now() はエポック秒を返すため * 1000 でミリ秒に変換し、toMillis() と単位を揃えています。シェル変数をクエリ文字列に埋め込む必要がなくなりました。
テスト2: IP分類関数(2026年6月追加)
プロンプト:
VPCフローログのsrcAddrをプライベート/パブリック/予約済みに分類してカテゴリ別のバイト数を集計するクエリを書いてください。
スキルなし:
fields
if(srcAddr like /^10\./ or srcAddr like /^172\.(1[6-9]|2[0-9]|3[01])\./ or ...,
"PRIVATE", ...)
正規表現で手動分類する30行超のクエリが生成されました。isReservedIP() に相当する分類がなく、2分類(Private/Public)のみです。
スキルあり:
fields @timestamp, srcAddr, bytes
| filter ispresent(srcAddr) and ispresent(bytes)
| fields @timestamp, srcAddr, bytes,
case(
isPrivateIP(srcAddr), "private",
isPublicIP(srcAddr), "public",
isReservedIP(srcAddr), "reserved",
"unknown"
) as addrCategory
| stats sum(bytes) as totalBytes by addrCategory
| sort totalBytes desc
| limit 50
組み込み関数で3分類を実現し、ispresent() や limit も付与された実用的なクエリが生成されました。
実環境での実行確認
テスト2について、スキルあり条件で生成されたクエリと同じIP分類関数を使った簡略化クエリをCloudWatch Logs Insightsで実行し、関数の戻り値と分類結果を確認しました。
実行環境:
- リージョン: ap-northeast-1
- ロググループ:
/test/ip-classification-verification(検証後削除済み) - ダミーデータ: 12件のJSON(RFC 5737 / RFC 1918 / RFC 6598 / link-local / Class E / パブリックIPで構成)
スキルありの実行結果:
スキルあり条件で生成されたクエリは case() で分類ラベルを付与する形式ですが、ここでは各関数の戻り値(0/1)を直接確認するために簡略化したクエリで実行しました。
filter ispresent(srcAddr)
| fields srcAddr, isPrivateIP(srcAddr) as isPrivate, isPublicIP(srcAddr) as isPublic, isReservedIP(srcAddr) as isReserved, bytes, action
| sort @timestamp desc
| limit 50
| srcAddr | isPrivate | isPublic | isReserved | 期待分類 | 判定 |
|---|---|---|---|---|---|
| 10.0.1.5 | 1 | 0 | 0 | Private (RFC 1918) | ✅ |
| 172.16.0.100 | 1 | 0 | 0 | Private (RFC 1918) | ✅ |
| 192.168.1.50 | 1 | 0 | 0 | Private (RFC 1918) | ✅ |
| 10.255.0.1 | 1 | 0 | 0 | Private (RFC 1918) | ✅ |
| 8.8.8.8 | 0 | 1 | 0 | Public | ✅ |
| 1.1.1.1 | 0 | 1 | 0 | Public | ✅ |
| 203.0.113.50 | 0 | 0 | 1 | Reserved (TEST-NET-3) | ✅ |
| 169.254.0.10 | 0 | 0 | 1 | Reserved (link-local) | ✅ |
| 198.51.100.25 | 0 | 0 | 1 | Reserved (TEST-NET-2) | ✅ |
| 192.0.2.1 | 0 | 0 | 1 | Reserved (TEST-NET-1) | ✅ |
| 100.64.0.1 | 0 | 0 | 1 | Reserved (CGN, RFC 6598) | ✅ |
| 240.0.0.1 | 0 | 0 | 1 | Reserved (Class E) | ✅ |
用意した12件のIPアドレスに対して、isPrivateIP() / isPublicIP() / isReservedIP() の戻り値は期待分類と12/12で一致しました。
スキルなしの実行結果:
正規表現ベースのクエリを同じデータで実行したところ、Reserved IP 5件(TEST-NET、CGN、Class E)をPublicと誤分類しました。link-local(169.254.0.10)は除外リストに含まれていたためPublicとはなりませんでしたが、Private/Publicいずれにも分類されず判定不可でした。誤分類5件と判定不可1件を合わせた6件が不正解で、正答率は6/12(50%)です。
| 観点 | スキルあり | スキルなし |
|---|---|---|
| 使用関数 | isPrivateIP() / isPublicIP() / isReservedIP() |
正規表現 |
| 分類カテゴリ | 3分類 (Private/Public/Reserved) | 2分類 (Private/Public) |
| 正答率 | 12/12 (100%) | 6/12 (50%) |
| 誤分類パターン | なし | Reserved IP → Public扱い5件、判定不可1件 |
テスト3: histogram(2026年6月追加)
プロンプト:
response_timeフィールドの分布をhistogramで可視化し最頻バケットを求めるクエリを書いてください。
スキルなし:
CloudWatch Logs Insightsには組み込みのhistogram()関数がないため、bin()を使って...
histogram() の存在を否定し、bin(response_time, 100) で代替するクエリを生成しました。公式リファレンスでは bin() は主に時刻フィールドの集約例で使用されており、数値フィールドの分布可視化には histogram() が適切です。
スキルあり:
filter ispresent(response_time)
| stats count(*) as cnt by histogram(response_time, 50)
| sort cnt desc
| limit 50
histogram(field, width) を by 句で正しく使用し、バケット幅50で分布を取得するクエリが生成されました。cnt の降順で並べているため、最も件数の多いバケットが先頭に表示されます。分布全体も確認できるよう limit 50 としています。
テスト4: parse CSV(2026年6月追加)
プロンプト:
CSV形式のログ(タイムスタンプ,レベル,サービス名,値)をパースしてサービス別の合計値を出すクエリを書いてください。
スキルなし:
parse @message /^(?<ts>[^,]+),(?<level>[^,]+),(?<service>[^,]+),(?<value>[^,\n]+)/
| stats sum(toNumber(value)) as total by service
正規表現による手動パースです。単純な形式であれば動作しますが、カラムが増えるとパターンの修正が必要で、クォート内にカンマを含むCSVなどには対応できません。
スキルあり:
fields @message
| parse @message csv as timestamp, level, service, value
| filter ispresent(value)
| stats sum(value) as totalValue by service
| sort totalValue desc
| limit 100
parse ... csv as ... 構文で簡潔にパースしています。カラムを追加する場合も、正規表現全体を書き換えるより、as の後にフィールド名を追加する形で対応しやすくなります。
テスト5: parse logfmt(2026年5月追加)
プロンプト:
logfmt形式のログ(level=info service=auth method=POST duration=234ms)からサービス別の平均レスポンスタイムを集計するクエリを書いてください。
スキルなし:
parse @message "service=* " as service
| parse @message "duration=*ms" as duration_str
| stats avg(toNumber(duration_str)) as avg_duration_ms by service
globパターンで個別にフィールドを抽出しています。フィールドの追加ごとに parse 行を追加する必要があります。
スキルあり:
parse @message logfmt as lf
| filter ispresent(lf.duration) and ispresent(lf.service)
| fields lf.service as service,
toNumber(replace(lf.duration, "ms", "")) as durationMs
| stats avg(durationMs) as avgResponseMs by service
| sort avgResponseMs desc
| limit 50
parse ... logfmt as lf で一括パースし、全フィールドにドット記法(lf.service、lf.duration)でアクセスしています。
総合結果
| テスト | スキルなし | スキルあり | 差分の核心 |
|---|---|---|---|
| 1. 時刻指定 | ❌ | ✅ | now() の存在を否定 vs now() / toMillis() を使用 |
| 1b. CLIスクリプト | △ | ✅ | シェル変数埋め込み vs クエリ内動的指定 |
| 2. IP分類 | △ | ✅ | 正規表現(50%正答) vs 組み込み関数(100%正答) |
| 3. histogram | ❌ | ✅ | histogram() の存在を否定 vs histogram() を使用 |
| 4. CSV parse | △ | ✅ | 正規表現 vs parse ... csv 構文 |
| 5. logfmt parse | △ | ✅ | glob パターン vs parse ... logfmt 構文 |
スキルなし条件では、❌(新コマンド・関数の存在を否定)が2件、△(代替手段で動作するが冗長または不正確)が4件でした。スキルあり条件では、全6件で対象の新コマンド・関数・構文拡張を使う出力が得られました。なお、実環境での実行確認はテスト2を対象に行っています。
まとめ
Kiro CLIのスキル機能を使い、CloudWatch Logs Insights QLの新コマンド・関数を含むクエリ生成がどう変わるかを比較しました。
スキルなし条件では、新コマンド・関数の存在を否定したり、正規表現などの代替手段を使用したりするケースがありました。一方、スキルあり条件では、スキル本体・エージェントプロンプト・ナレッジベースを参照し、全6件で対象構文を使ったクエリ出力が得られました。
テスト2の実環境確認では、isPrivateIP() / isPublicIP() / isReservedIP() の戻り値が、用意した12件の期待分類と一致しました。今回生成された正規表現による手動分類クエリと比べても、組み込み関数を使うことでReserved IPを含む分類を簡潔に表現できました。
今回の結果は、用意したスキル本体・エージェントプロンプト・ナレッジベースの内容に依存します。また、スキルあり条件では入力トークンが増加し、ispresent()、sort、limit などの指定もクエリの目的に応じて調整が必要です。
今回のCloudWatch Logs Insights QLの新構文検証では、スキルとナレッジベースの組み合わせは有効な選択肢でした。







