CloudWatch Logs Insightsに追加された新コマンド・関数23個を検証してみた(2026年6月版)
はじめに
2026年6月8日、Amazon CloudWatch Logs Insightsに23個の新コマンド・関数が追加されました。
前回の記事では5月21日発表の13個を紹介しましたが、わずか1か月足らずで更に大幅な追加です。
| 発表日 | 追加数 | 主なカテゴリ |
|---|---|---|
| 5/21 | 13個 | 文字列操作、エンコード/デコード、logfmt パース、座標計算 |
| 6/8 | 23個 | ハッシュ、IP判定、型変換、時系列分析、CSV/XMLパース、コマンド拡張 |
5月と6月で追加された機能の傾向を比較すると、クエリ言語としての方向性が見えてきます。
| 5月(13個) | 6月(23個) | |
|---|---|---|
| 性格 | ログ閲覧・整形ツール | ログ分析基盤 |
| 主な作業 | 「読む・探す」 | 「集計する・判定する」 |
| 代替していたもの | 目視確認、手動デコード | Athena/pandas/Splunk での後処理 |
| 典型ユースケース | 障害時の初動調査 | セキュリティ監査、トラフィック分析、SLO集計 |
5月が「ログを読みやすくする」機能群だったのに対し、6月は「ログから答えを出す」機能群と言えます。
今回追加された新機能の一覧は以下のとおりです。
| 分類 | 項目 |
|---|---|
| Hash functions | md5, sha256 |
| String functions | strcontains(case-insensitive対応), split |
| Conditional logic | if |
| Conversion functions | toNumber, toInt, toLong, toDouble |
| IP functions | ipv4ToNumber, isPrivateIP, isPublicIP, isReservedIP |
| Analytics functions | rate, count_over_time, sum_over_time, offset, histogram |
| Parse functions | parse CSV, parse XML, parse multi, values, addtotals |
| その他 | limit any N, statsコマンド最大10段 |
※ 公式は「23 new query commands and functions」としています。上記はparse CSV/XML/multi等を個別に展開しているため項目数が前後します。公式は構文拡張等をまとめてカウントしており、本記事では公式の「23」をそのまま使用します。
本記事ではこれらを実際に動かし、動作結果を確認していきます。
検証環境
- リージョン: us-east-1
- ロググループ:
/test/insights-new-2026-06 - テストデータ: JSON形式、CSV形式、XML形式、時系列データ。架空のユーザーID・サービス名・IPアドレスを使用しています(IPはRFC 5737 TEST-NET、RFC 1918プライベート範囲、8.8.8.8等の公知アドレスで構成)
ハッシュ関数(md5, sha256)
md5 と sha256 はフィールドの値からハッシュ値を生成する関数です。ログ内のユーザーIDなどをハッシュ化して表示したい場合に使えます。
fields user_id, md5(user_id) as md5_hash, sha256(user_id) as sha256_hash
| limit 3
| user_id | md5_hash | sha256_hash |
|---|---|---|
| usr_010 | 2fb6c8adce410db19ed04a7157b1ebd0 | 34f0365ae65242b4664ad6a1e4fe941c77caf56d7bd5aca88f1e9c6927012207 |
| usr_009 | 3b24508aecac732b2dbf6d4e4bf9c4c2 | 8c231f143bc453047665350be67bc92029fe34375ddd45bee71164fcb278315c |
| usr_008 | 136ea0f2a0158fcbbd873dead2d60963 | a8d79cc679fa29d08b483641f5d4b01faea2e81aa44edc7684fc2b936f32437c |
md5は128bit(16進32文字)、sha256は256bit(16進64文字)の文字列が返されました。
文字列関数(strcontains, split)
split
split は文字列を指定した区切り文字で配列に分割します。
fields tags, split(tags, ',') as tag_array
| limit 3
| tags | tag_array |
|---|---|
| prod,warning,us-east-1 | ["prod","warning","us-east-1"] |
| prod,normal,ap-northeast-1 | ["prod","normal","ap-northeast-1"] |
| prod,critical,ap-northeast-1 | ["prod","critical","ap-northeast-1"] |
カンマ区切りのタグ文字列が配列形式(["prod","warning","us-east-1"])で展開されました。
strcontains
strcontains は文字列に特定の部分文字列が含まれるかを判定します。ドキュメントでは第3引数に true を指定するとcase-insensitive(大文字小文字を区別しない)検索になるとされています。
fields service,
strcontains(service, 'auth') as has_auth_lower,
strcontains(service, 'AUTH') as has_AUTH_upper,
strcontains(service, 'AUTH', true) as has_AUTH_ci
| filter ispresent(service)
| sort service
| limit 5
| service | has_auth_lower | has_AUTH_upper | has_AUTH_ci |
|---|---|---|---|
| api-gateway | 0 | 0 | 0 |
| auth-service | 1 | 0 | 0 |
| cdn-edge | 0 | 0 | 0 |
| data-pipeline | 0 | 0 | 0 |
| geo-service | 0 | 0 | 0 |
基本動作(第1・第2引数のみ)は正常です。auth-service に対して strcontains(service, 'auth') が 1 を返しています。
しかし、筆者環境では第3引数 true(case-insensitive モード)の効果を確認できませんでした。strcontains(service, 'AUTH', true) は auth-service に対して 0 を返しており、大文字小文字を区別したまま動作しているように見えました。引数自体はシンタックスエラーにならず受け付けられましたが、指定しても結果は変わりませんでした。
条件ロジック(if)
if は条件に基づいて値を返す関数です。if(条件, 真の場合の値, 偽の場合の値) の構文で、三項演算子のように使えます。
fields service, response_time,
if(toNumber(response_time) > 1000, 'slow', 'fast') as speed
| limit 5
| service | response_time | speed |
|---|---|---|
| queue-worker | 2045 | slow |
| search-service | 156 | fast |
| auth-service | 8901 | slow |
| notification | 67 | fast |
| geo-service | 312 | fast |
レスポンスタイムが1000msを超えるリクエストを slow と分類できました。
前回(5月)追加の case 関数との使い分けとしては、if は単一条件の二択、case は多分岐に適しています。
変換関数(toNumber, toInt, toLong, toDouble)
文字列フィールドを数値型に変換する関数が4種類追加されました。それぞれの型変換の違いを確認します。
整数値の変換
fields response_time,
toInt(response_time) as rt_int,
toLong(response_time) as rt_long,
toDouble(response_time) as rt_double,
toNumber(response_time) as rt_number
| filter ispresent(response_time)
| limit 5
| response_time | rt_int | rt_long | rt_double | rt_number |
|---|---|---|---|---|
| 234 | 234 | 234 | 234 | 234 |
| 89 | 89 | 89 | 89 | 89 |
| 1523 | 1523 | 1523 | 1523 | 1523 |
| 5002 | 5002 | 5002 | 5002 | 5002 |
| not_a_number | (null) | (null) | (null) | (null) |
整数値の場合は4関数とも同じ結果でした。変換に失敗した場合(not_a_number)はエラーにならずnullが返ります。
小数値の変換(型の違いが顕著)
parse @message /latency=(?<lat_val>[\d.]+)/
| display lat_val,
toInt(lat_val) as lat_int,
toLong(lat_val) as lat_long,
toDouble(lat_val) as lat_double,
toNumber(lat_val) as lat_number
| filter ispresent(lat_val)
| lat_val | lat_int | lat_long | lat_double | lat_number |
|---|---|---|---|---|
| 890.12 | 890 | 890 | 890.12 | 890.12 |
| 45.7 | 45 | 45 | 45.7 | 45.7 |
| 123.456 | 123 | 123 | 123.456 | 123.456 |
小数を含む値で違いが現れました。
- toInt / toLong: 小数点以下を切り捨てました(正の値のみ検証。負数でのfloor vs truncateの挙動は未確認です)
- toDouble / toNumber: 小数点以下を保持し、今回の検証範囲では同様の結果でした
- 型名からは、toIntとtoLongの差は32bit整数の範囲(約21億)を超える値などで現れる可能性がありますが、今回のテストデータでは差を確認できませんでした
用途としては、小数精度が不要な集計には toInt/toLong、小数を保持したい計算には toDouble/toNumber を使い分けることになります。
IP関数(ipv4ToNumber, isPrivateIP, isPublicIP, isReservedIP)
IPアドレスの数値変換と分類判定を行う4つの関数が追加されました。VPCフローログやALBアクセスログの分析に便利です。
fields ip,
ipv4ToNumber(ip) as ip_num,
isPrivateIP(ip) as is_private,
isPublicIP(ip) as is_public,
isReservedIP(ip) as is_reserved
| limit 10
| ip | ip_num | is_private | is_public | is_reserved |
|---|---|---|---|---|
| 10.255.255.1 | 184549121 | 1 | 0 | 0 |
| 52.194.x.x | 885131796 | 0 | 1 | 0 |
| 192.0.2.1 | 3221225985 | 0 | 0 | 1 |
| 169.254.169.254 | 2852039166 | 0 | 0 | 1 |
| 100.64.0.1 | 1681915905 | 0 | 0 | 1 |
| 8.8.8.8 | 134744072 | 0 | 1 | 0 |
| 172.16.0.10 | 2886729738 | 1 | 0 | 0 |
| 10.0.0.55 | 167772215 | 1 | 0 | 0 |
| 203.0.113.50 | 3405803826 | 0 | 0 | 1 |
| 192.168.1.100 | 3232235876 | 1 | 0 | 0 |
※ パブリックIPの一部は下位オクテットをマスクしています(実際の検証では完全なIPアドレスを使用)。
今回用意したIPアドレスについては、期待した分類結果になることを確認できました。結果から読み取れる分類基準は以下のとおりです。
- Private(
isPrivateIP= 1): RFC 1918で定義されたプライベートアドレス範囲- 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16
- Reserved(
isReservedIP= 1): 特殊用途に予約されたアドレス範囲- 169.254.0.0/16(リンクローカル)、100.64.0.0/10(CGN / Shared Address)
- 192.0.2.0/24(TEST-NET-1)、203.0.113.0/24(TEST-NET-3)
- Public(
isPublicIP= 1): 今回試した範囲では、Private / Reservedに該当しない一般的なパブリックIPアドレスが1と判定されました
今回検証したIPv4アドレスの範囲では、3つの分類のいずれか1つだけが 1 になり、排他的な関係でした。ipv4ToNumber はIPアドレスを32bit整数に変換する関数です。IPレンジによるフィルタリング(ipv4ToNumber(ip) >= X and ipv4ToNumber(ip) <= Y)に活用できます。
分析関数(rate, count_over_time, sum_over_time, offset, histogram)
時系列データの分析に関連する5つの機能が追加されました。
count_over_time
count_over_time は時間範囲ごとのレコード数をカウントします。
filter metric = 'cpu_usage'
| stats count_over_time(*) as cot by bin(2m)
| bin(2m) | cot |
|---|---|
| 2026-06-09 16:20:00.000 | 3 |
| 2026-06-09 16:18:00.000 | 4 |
| 2026-06-09 16:16:00.000 | 4 |
| 2026-06-09 16:14:00.000 | 4 |
| 2026-06-09 16:12:00.000 | 4 |
| 2026-06-09 16:10:00.000 | 1 |
今回のbin集計条件では count(*) と同等の結果でした。
sum_over_time
sum_over_time は時間範囲ごとの合計値を算出します。
filter metric = 'cpu_usage'
| stats sum_over_time(value) as sot by bin(2m)
| bin(2m) | sot |
|---|---|
| 2026-06-09 16:20:00.000 | 277 |
| 2026-06-09 16:18:00.000 | 447 |
| 2026-06-09 16:16:00.000 | 383 |
| 2026-06-09 16:14:00.000 | 408 |
| 2026-06-09 16:12:00.000 | 384 |
| 2026-06-09 16:10:00.000 | 143 |
こちらも sum(value) と同等の結果でした。count_over_time/sum_over_time は時系列分析向けの命名と見られます。ただし今回のbin集計条件では count(*)/sum() との動作の違いを確認できず、完全に同じ動作かどうかは断定できません。
histogram
histogram は by 句のグルーピング関数として使い、数値フィールドをバケットに分けて集計します。
filter metric = 'cpu_usage'
| stats count(*) as cnt by histogram(value, 50)
| histogram(value, 50) | cnt |
|---|---|
| 50 | 10 |
| 100 | 10 |
第2引数がバケット幅で、結果にはバケットの下限値が表示されます。バケット幅を25に変更すると、より細かい分布が見えます。
filter metric = 'cpu_usage'
| stats count(*) as cnt by histogram(value, 25)
| histogram(value, 25) | cnt |
|---|---|
| 50 | 2 |
| 75 | 8 |
| 100 | 6 |
| 125 | 4 |
histogram は stats の集計関数ではなく by 句のグルーピング関数である点に注意してください。bin が時間軸のバケット化なのに対し、histogram は数値軸のバケット化です。
offset
offset は bin() の修飾子で、bin境界のアライメント(開始位置)をずらします。
filter metric = 'cpu_usage'
| stats count(*) as cnt by bin(5m) offset 5m
| bin(5m) | cnt |
|---|---|
| 2026-06-09 16:20:00.000 | 3 |
| 2026-06-09 16:15:00.000 | 10 |
| 2026-06-09 16:10:00.000 | 7 |
構文は by bin(5m) offset 5m のようにbinの後ろに付けます。関数ではなく修飾子です。offset を使うとbin境界の開始位置をずらせるため、業務時間帯に合わせた集計区切りを設定できます。
rate
rate はbin内の数値フィールドの変化率を算出する関数です。rate(field, period) の構文で、第2引数に時間単位(1s, 1m, 2m 等)を指定します。
filter metric = 'cpu_usage'
| stats rate(value, 1s) as rate_1s, rate(value, 1m) as rate_1m, rate(value, 2m) as rate_2m by bin(5m)
| bin(5m) | rate_1s | rate_1m | rate_2m |
|---|---|---|---|
| 2026-06-09 18:35:00.000 | 20 | 0.3333 | 0.1667 |
| 2026-06-09 18:30:00.000 | 20 | 0.3333 | 0.1667 |
| 2026-06-09 18:25:00.000 | 20 | 0.3333 | 0.1667 |
テストデータは2分間隔で値が+10ずつ増加する時系列です。結果から、periodによる値の比率は rate_1s : rate_1m : rate_2m = 60 : 1 : 0.5 でした。rate はbin内のフィールド値の合計変化量をperiodの秒数で割った値を返しています。periodが短いほど大きい値になります。
なお、第2引数に数値(60)を指定するとエラーになります。1m のような時間単位で指定する必要があります。
パース構文(parse CSV, parse XML, parse multi, values, addtotals)
ログのパース機能が大幅に拡張されました。CSV、XML、複数マッチの3つの新しいパースモードと、集計補助の values・addtotals が追加されています。
parse CSV
parse @message CSV as alias1, alias2, ... の構文でCSV形式のログを列ごとに分割します。
filter @logStream = 'test-csv-stream'
| parse @message CSV as ts, lvl, svc, val
| display ts, lvl, svc, val
| ts | lvl | svc | val |
|---|---|---|---|
| 2026-06-10T00:04:00Z | DEBUG | cache | 10 |
| 2026-06-10T00:03:00Z | INFO | search | 50 |
| 2026-06-10T00:02:00Z | WARN | payment | 175 |
| 2026-06-10T00:01:00Z | ERROR | auth-service | 250 |
カンマ区切りの各値が順番にエイリアスへ格納されました。ヘッダー行とデータ行の区別はなく、全行がパース対象です。
parse XML
XML形式のログは、XPath風のパス式でフィールドを抽出します。
filter @message like /<event>/
| parse @message XML '/event/level' as xlevel
| parse @message XML '/event/service' as xsvc
| parse @message XML '/event/code' as xcode
| display xlevel, xsvc, xcode
| xlevel | xsvc | xcode |
|---|---|---|
| WARN | payment | 429 |
| INFO | api-gw | 200 |
| ERROR | auth | 401 |
構文は parse @message XML '/要素/パス' as alias です。今回確認した範囲では /event/level のような単純な要素パスで値を取得できました。parse @message XML as doc のようなドキュメントオブジェクト全体の取得やドット記法でのアクセスはエラーになりました。複数フィールドを抽出する場合は、上記のように parse を複数回パイプして使用します。
parse multi
parse multi は正規表現の1行内の全マッチを個別レコードに展開します。key=value形式のログパースに威力を発揮します。
filter @message like /^level=/
| parse @message /(?<kname>\w+)=(?<kval>\S+)/ multi
| stats count(*) by kname
| kname | count(*) |
|---|---|
| level | 3 |
| service | 3 |
| latency | 3 |
| request_id | 3 |
元データは3行で、各行に4つのkey=valueペアがあります。multi を付けることで3行 × 4ペア = 12レコードに展開され、キー名ごとの集計ができました。
multi なしの場合は1行につき最初のマッチのみが抽出されます。
parse @message /(?<kname>\w+)=(?<kval>\S+)/
| display kname, kval
| kname | kval |
|---|---|
| level | WARN |
| level | INFO |
| level | ERROR |
今回試した正規表現ベースの parse multi では、名前付きキャプチャグループ (?<name>...) を使うことで期待通りに抽出できました。一方、ドキュメントに記載のある as alias multi 構文は筆者環境ではシンタックスエラーになりました。
values
values はグループごとの重複なし(distinct)の値をまとめて返す集計関数です。
filter ispresent(service) and ispresent(level)
| stats values(service) as services by level
| level | services |
|---|---|
| INFO | test-convert, api-gateway, geo-service, notification, search-service |
| WARN | payment-service, queue-worker |
| ERROR | auth-service, cdn-edge |
| DEBUG | data-pipeline |
今回のAPI結果ではカンマ区切りの文字列表現として確認できました。グループ内の一意な値を一覧する場合に便利です。
addtotals
addtotals は各行の数値フィールドを合計した列を追加するコマンドです。なお、表示されている列だけでなくクエリ中に存在する全数値フィールドも合算対象になるため、表示列の単純合計と Total の値は一致しないことがあります。
filter ispresent(response_time)
| fields toNumber(response_time) as rt, toNumber(response_time) * 2 as rt2
| addtotals
| limit 5
| rt | rt2 | Total |
|---|---|---|
| 2045 | 4090 | 8180 |
| 156 | 312 | 624 |
| 8901 | 17802 | 35604 |
| 67 | 134 | 268 |
| 234 | 468 | 936 |
デフォルトでは Total という列名で行合計が追加されます。addtotals fieldname=RowSum で列名をカスタマイズ可能です。
col=true を指定すると列合計行も追加されるはずですが、get-query-results APIのレスポンスには列合計行は含まれませんでした。コンソールUIでのみ表示される可能性があります。
その他(limit any, statsコマンド最大10段)
limit any
limit any は順序保証なしに任意のN件を返す構文です。通常の limit が直前の sort 順(未指定時はタイムスタンプ降順と見られる動作)で先頭N件を返すのに対し、limit any は順序が不要な場合により早く結果を得られる可能性があります。
fields service, level, ip | limit any 2
| service | level | ip |
|---|---|---|
| search-service | INFO | 10.255.255.1 |
| cdn-edge | ERROR | 52.194.x.x |
大量のログがあるロググループで素早くサンプルを取得したい場合に有用です。
statsコマンド最大10段
stats をパイプで複数回つなげられる範囲が拡張され、Standard log classでは最大10段まで使用できるようになりました。
filter ispresent(service)
| stats count(*) as cnt by service, level
| stats sum(cnt) as level_total by level
| stats max(level_total) as max_level_total, min(level_total) as min_level_total
| max_level_total | min_level_total |
|---|---|
| 5 | 1 |
3段のstatsをパイプで接続しています。1段目でサービス×レベルごとのカウント、2段目でレベルごとの合計、3段目で最大・最小を算出しました。
ドキュメントによると、Standard log classでは最大10段、Infrequent Access log classでは最大2段まで使用可能です。後続の stats では前段で定義したフィールドのみ参照可能で、sort や limit は最後の stats の後に配置する必要があります。
実用的な例として、時間帯ごとのメッセージ文字数の集計から傾向を把握するパターンを紹介します。
fields strlen(@message) as msg_len
| stats sum(msg_len) as total_chars by bin(5m)
| stats max(total_chars) as peak, min(total_chars) as lowest, avg(total_chars) as average
| peak | lowest | average |
|---|---|---|
| 1586 | 431 | 935.5 |
5分ごとのメッセージ文字数の合計を算出し、その中からピーク・最小・平均を取得しています。ログがASCII中心であれば、おおよそのメッセージサイズ傾向を見る用途にも使えます。このような「集計結果をさらに集計する」パターンが1クエリで完結するようになりました。
まとめ
5月に13個、6月に23個と、CloudWatch Logs Insightsのクエリ言語は短期間で大きく拡充されています。
今回追加された機能では、IP分類、CSV/XMLパース、histogram、複数段の stats などが含まれていました。ログを検索するだけでなく、集計・判定・傾向把握までクエリ内で行いやすくなっています。検証した範囲では、従来Athenaや外部ツールで後処理していたような分析の一部も、Logs Insightsだけで完結できる場面が増えそうです。
一方で、記事執筆時点の筆者環境では、strcontains の第3引数によるcase-insensitive検索は期待通りに動作しませんでした。
CloudWatch Logs Insightsは、「ログを検索・確認するツール」から、より分析用途にも使いやすいクエリ環境へと進化している印象です。今回の追加により、日々の調査や一時分析で使える場面がさらに広がりそうです。
参考リンク







