Claude Media
Claude CodeでCloudWatch Logsを集計しエラーの急増を見つける

Claude CodeでCloudWatch Logsを集計しエラーの急増を見つける

aws logs start-queryとget-query-resultsだけを許可する権限の切り方と、5分刻みのエラー件数をClaude Codeに集計させるクエリの書かせ方を示します。

CloudWatch Logs Insightsのクエリは、Claude Codeに書かせて実行までさせられます。必要な権限はaws logs start-queryとaws logs get-query-resultsの2つだけです。許可ルールを2行書けば、ログの検索と集計は確認なしで回り、ほかのawsコマンドは従来どおり承認を求められます。

この記事は、その権限の切り方と、エラーが急増した時間帯を5分刻みで見つけるクエリの書かせ方をまとめます。AWSのログ基盤そのものの設計は扱いません。

許可するのは2つのサブコマンドだけ

Logs Insightsは、コンソールを使わなくてもAPIから使えます。AWSのドキュメントには、ネットワーク側でWebSocketを許可できない環境ではコンソールのLogs Insights画面を開けず、APIでクエリ機能を使うと書かれています。CLIでは次の2段階です。

手順

CLIでのクエリ実行は2段階

  1. 1

    start-queryでクエリを投げる

    ログ対象、開始・終了時刻、クエリ文字列を渡します。戻り値はqueryIdだけです。

  2. 2

    get-query-resultsで結果を取る

    --query-idに先ほどの値を渡します。get-query-resultsはクエリを開始せず、結果を返すだけです。

この2つに対応するルールを、プロジェクトの.claude/settings.jsonに置きます。

{
  "permissions": {
    "allow": [
      "Bash(aws logs start-query *)",
      "Bash(aws logs get-query-results *)"
    ]
  }
}

末尾の*の前にあるスペースが効きます。Bash(aws logs start-query *)はaws logs start-queryで始まるコマンドにだけ一致します。サブコマンドの手前までを固定し、後ろの引数だけを*にするのが権限ドキュメントの書き方です。

ほかのawsコマンドはどうなるか

aws logs delete-log-groupやaws s3 rmは、上の2行に一致しません。許可ルールに当たらないコマンドは、その場で承認を求められます。拒否したいコマンドがあれば、denyに個別に足します。

{
  "permissions": {
    "allow": [
      "Bash(aws logs start-query *)",
      "Bash(aws logs get-query-results *)"
    ],
    "deny": [
      "Bash(aws logs delete-log-group *)"
    ]
  }
}

注意点が1つあります。denyにBash(aws *)のような広いルールを置くと、より細かいallowは効かなくなります。ルールの評価はdeny、ask、allowの順で、最初に一致したものが結果を決めます。allowで例外を切り出すことはできません。「awsは全部止めて、logsの2つだけ通す」という書き方は成立しないので、allowを2つ置いて、残りは承認制に任せます。

オプションの置き場所で一致が変わる

ルールはaws logs start-queryという並びを前提にします。aws --region ap-northeast-1 logs start-queryのようにサブコマンドの前へオプションを置くと、並びが変わるためルールに一致しません。この場合は許可ルールが適用されず、承認を求められます。

start-queryのSynopsisには--regionや--profileが並んでいるので、サブコマンドの後ろに置いても受け付けられます。依頼文には「オプションはサブコマンドの後ろに書く」と入れておきます。

先頭にAWS_PROFILE=prodのような環境変数の代入を付ける書き方も避けます。許可ルールが読み飛ばすのは、既知の安全な変数の代入だけです。それ以外の変数を付けると、allowルールは一致しません。

権限では止められないもの

2行のルールで確実に絞れるのは、実行できるサブコマンドです。引数の中身までは絞れません。

  • スキャン量: Logs Insightsの料金は、スキャンした非圧縮ログのデータ量で決まります。対象の期間とロググループを広く取ると、そのぶん課金されます。Bash(aws logs start-query *)は、期間が1時間でも1か月でも通ります。
  • コマンドの別形態: 権限ドキュメントによれば、BashのルールはClaudeが書いたコマンド文字列に一致します。同じプログラムを別の形で呼ぶと一致しないことがあります。たとえば/usr/bin/curlはBash(curl *)を通り抜けます。引数を絞る書き方は脆いとも説明されています。

スキャン量を減らす手段としては、フィールドインデックスがあります。ログに共通して入っているフィールド(たとえばtransactionId)にインデックスを作っておくと、クエリがそのフィールドを条件にしたとき、該当しないログイベントを読み飛ばして処理するデータを減らします。AWSのAPIリファレンスには、filterIndex transactionId = 'tx-001'のようにクエリへ書く例があります。get-query-resultsのstatisticsには、読み飛ばした量の推定値としてestimatedBytesSkippedが入っているので、効いているかを確かめられます。インデックスの作成はPutIndexPolicyという別の操作で、上の2行の許可には含まれません。作るかどうかは、人が承認する側に残しておけます。

どちらも、権限の外側で受けます。スキャン量はAWS側の請求アラートや、依頼文での期間の指定で抑えます。コマンドの別形態を含めて止めたいなら、IAMの権限を読み取りだけに絞った認証情報を使います。AWS CLIをMCP経由で動かす構成のIAM設計はaws-api-mcp-serverの権限設計が詳しい記事です。ファイルとネットワークをOSレベルで制限するサンドボックスも、権限ルールと併用できます。

依頼の型をCLAUDE.mdに書く

許可だけ通しても、Claudeが毎回違うクエリを書くと比較できません。調べ方のルールをCLAUDE.mdに固定します。例えば次の形です。

## ログ調査のルール
- ログはaws logs start-queryで投げ、get-query-resultsで取る。ほかのawsコマンドは使わない
- --regionと--profileは、必ずサブコマンドの後ろに書く
- 期間は必ずエポック秒で指定する。指定がなければ直近3時間にする
- クエリは毎回statsで集計する。生ログの全件取得はしない
- 結果のstatusがCompleteになるまで取り直す。statisticsのbytesScannedも報告する

最後の行が実質的な歯止めです。get-query-resultsのレスポンスにはstatisticsがあり、その中にrecordsScannedとbytesScannedが入っています。毎回スキャン量を報告させると、広すぎる期間のクエリに気づけます。

エラーの急増を5分刻みで集計させる

依頼の例は次のとおりです。

/app/prod/apiのログから、直近3時間のERRORを5分刻みで数えて。件数が跳ねた時間帯を教えて。

Claudeが組み立てるクエリは、AWSのサンプルクエリにある「時間帯ごとの件数」の型です。例外の件数を1時間刻みで数えるものと、時刻順に並べるものが公式に載っています。それを5分刻みの形にすると次のとおりです。

filter @message like /ERROR/
| stats count(*) as errorCount by bin(5m) as t
| sort t

bin(5m)が時間の区切りで、sort tで時刻順に並べます。公式の例はbin(1h)ですが、同じ書き方で粒度だけを変えられます。件数の多い時間帯を先に見たいなら、sort errorCount descとlimit 10に差し替えます。

エラーが0件の区間が抜け落ちる

このクエリで気をつけたいのは、filterでERRORだけに絞ってからstatsで数えている点です。ERRORが1件もなかった5分間は、集計結果に行として現れません。結果を上から読むと、静かな時間帯が飛ばされて、エラーが続いていたように見えます。急増の前後を比べるときは、この欠けが誤読のもとになります。

fillmissingコマンドを使うと、stats ... by bin()の後で、空の時間区間に行を差し込めます。値はwith 0 for errorCountのように、フィールドごとに指定できます。

filter @message like /ERROR/
| stats count(*) as errorCount by bin(5m) as t
| fillmissing with 0 for errorCount
| sort t

AWSのドキュメントにある例は、1分刻みのavg_latencyを0で埋めるものです。ここでは、同じ書き方を件数に当てています。依頼文に「0件の区間も0として出して」と書いておけば、Claudeがこの行を入れてくれます。

実行の流れ

Claudeが実行するコマンドの形は次のとおりです。<開始>と<終了>には、エポック秒(1970年1月1日0時0分0秒UTCからの秒数)が入ります。

aws logs start-query \
  --log-group-name /app/prod/api \
  --start-time <開始> --end-time <終了> \
  --query-string 'filter @message like /ERROR/ | stats count(*) as errorCount by bin(5m) as t | sort t' \
  --region ap-northeast-1

戻り値はqueryIdです。続けて結果を取ります。

aws logs get-query-results --query-id <queryId> --region ap-northeast-1

--start-timeと--end-timeは必須で、どちらも「1970年1月1日0時0分0秒UTCからの秒数」で指定します。ミリ秒ではありません。時刻の計算に使うdateは、Claude Codeが読み取り専用として扱うコマンドの一覧(ls、cat、grepなど)に挙がっていません。計算でプロンプトが出る場合は、承認するか、Bash(date *)を許可に足します。

結果の読み方

返ってくるresultsは、配列の配列です。ログのレコードが1つずつ配列で、各要素にフィールド名と値の組が入ります。上のクエリなら、各行にtとerrorCountが並びます。

件数が跳ねた時間帯を見つけたら、次の一手として、その時間帯だけに期間を絞って@messageの中身を見る依頼を重ねます。

filter @message like /ERROR/
| stats count(*) as n by @message
| sort n desc
| limit 10

エラー文言ごとの件数を出すクエリです。急増の正体が、特定のエラー1種類なのか、全体に広がっているのかが分かります。

つまずきやすい点

落とし穴

Runningのまま結果を読んでしまう

get-query-resultsのstatusがRunningのとき、返るのは途中までの結果です。ScheduledかRunningなら、少し待って取り直します。statusはCancelled、Complete、Failed、Running、Scheduled、Timeout、Unknownのいずれかです。最終結果として読んでよいのはCompleteのときです。

ほかにも、次の制限があります。

  • 時間切れ: クエリは60分で時間切れになります。長引くなら、期間を狭めるか、クエリを分けます。
  • 同時実行数: 1アカウントで同時に動かせるLogs Insightsのクエリは100件です。ダッシュボードに追加したクエリも数えます。
  • 結果の保管: クエリ結果は7日間取得できます。
  • ロググループ作成前のログ: ロググループの作成時刻より古いタイムスタンプのログイベントには、Logs Insightsから届きません。
  • 取得件数: --limitは最大100,000件で、1回のget-query-resultsが返すのは最大10,000件です。続きはnextTokenで取ります。
  • クエリの構文エラー: 構文が不正だとMalformedQueryExceptionが返ります。Claudeにはそのエラーを読ませて、クエリを直させます。

statsで集計すれば、返る行数は時間帯の数で済みます。取得件数の上限に当たりにくいのも、集計から入る利点です。

同じ考え方は他のログ基盤にも使える

読み取りだけを許可し、集計から入って、スキャン量を報告させる。この流れは、AWS以外のログ基盤でも変わりません。SplunkならClaudeとSplunkを連携してSPLクエリを実行する方法があります。承認と実行を分けたいAWSの作業なら、AWS Lambdaをdeployだけ承認制にする構成も同じ発想です。

まとめ

CloudWatch Logs Insightsの調査をClaude Codeに任せる前提は、許可をstart-queryとget-query-resultsの2つに絞ることです。権限が決めるのは実行できるサブコマンドまでで、スキャン量は依頼文とAWS側の仕組みで抑えます。急増の特定は、まずbin(5m)の集計で時間帯を絞り、次にエラー文言別の集計で原因を探す、という二段で足ります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/permissions
  2. docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html
  3. docs.aws.amazon.com/cli/latest/reference/logs/start-query.html
  4. docs.aws.amazon.com/cli/latest/reference/logs/get-query-results.html
  5. docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax-examples.html
  6. docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax-Fillmissing.h…
この記事を共有:XはてブLinkedIn