Claude CodeでCloudWatch Logsの費用を下げる — 取り込み量の順位付けと保持期間
IncomingBytesでロググループを取り込み量順に並べ、保持期間をTerraformで設定し、DEBUGログの出力元をClaude Codeに探させる手順です。
CloudWatch Logsの請求を下げる手は、保持期間を短くすることと、取り込むログを減らすことの2つです。効く場所が違います。保持期間は保存にかかる分、ログレベルの見直しは取り込みにかかる分を減らします。
この記事は、取り込み量が多いロググループをIncomingBytesで順位付けするところから始めます。続いて保持期間をTerraformで固定し、DEBUGログの出力元をClaude Codeにコードから探させる流れを示します。障害調査のクエリはClaude CodeでCloudWatch Logsを集計しエラーの急増を見つけるの守備範囲なので、ここでは費用の観点だけを扱います。
先に分かれ目を押さえる
保持期間を短くしても、取り込み料金は下がりません。取り込み済みのログに対する課金は、すでに発生しているからです。
どちらの費用に効く手か
保持期間を短くする
古いログが消えるぶん、保存するデータ量が減ります。デプロイ済みのロググループにもその場で効きます。
ログレベルを上げる・出力を減らす
そもそも送らないので、取り込みと保存の両方が減ります。アプリケーション側の変更が要ります。
AWSのドキュメントによれば、ロググループの保持期間は既定で無期限です。設定しないまま放置されたロググループは、保存量が増え続けます。まず順位を見て、効く場所を決めます。
取り込み量の多いロググループを順位付けする
ロググループ別の取り込み量は、AWS/Logs名前空間のIncomingBytesメトリクスに入っています。ディメンションはLogGroupName、統計はSum、単位はバイトです。値は圧縮前のバイト数で、請求書の数字とそのまま一致する保証はありません。順位を付けるための目安として使います。
読み取りだけを許可する
Claude Codeに調べさせる前に、実行してよいコマンドを絞ります。ここで必要なのは、ロググループの一覧とメトリクスの取得だけです。
{
"permissions": {
"allow": [
"Bash(aws logs describe-log-groups *)",
"Bash(aws cloudwatch get-metric-statistics *)",
"Bash(bash scripts/cwl-top-ingest.sh *)"
],
"deny": [
"Bash(aws logs put-retention-policy *)",
"Bash(aws logs delete-log-group *)"
]
}
}denyにput-retention-policyを入れているのは、保持期間の変更をコマンドで直接やらせないためです。ルールはdeny、ask、allowの順に評価され、最初に一致したものが結果を決めます。保持期間は次の節のとおりTerraformで変えるので、CLIからの変更は止めておきます。
もう1つ、ループで複数のコマンドを並べると、複合コマンドとして扱われます。権限ドキュメントによると、区切り文字でつながったコマンドは、すべての部分が許可されていないと確認なしでは動きません。そこでループはシェルスクリプトに閉じ込め、スクリプト1本を許可します。
順位付けのスクリプト
次のスクリプトは、期間を引数で受け取り、ロググループごとのIncomingBytes合計を多い順に並べます。
#!/usr/bin/env bash
# 使い方: bash scripts/cwl-top-ingest.sh <開始 ISO8601> <終了 ISO8601>
set -euo pipefail
for g in $(aws logs describe-log-groups \
--query 'logGroups[].logGroupName' --output text); do
bytes=$(aws cloudwatch get-metric-statistics \
--namespace AWS/Logs --metric-name IncomingBytes \
--dimensions Name=LogGroupName,Value="$g" \
--start-time "$1" --end-time "$2" \
--period 86400 --statistics Sum \
--query 'sum(Datapoints[].Sum)' --output text)
printf '%s\t%s\n' "${bytes:-0}" "$g"
done | sort -rn | head -20--periodは86400秒(1日)にしています。get-metric-statisticsは、開始時刻が15日より前だと期間に5分の倍数、63日より前だと1時間の倍数を求めます。1回の呼び出しで返るデータポイントは最大1,440件です。1日刻みなら、直近1か月を見ても30件で収まります。
実行はClaude Codeに任せられます。依頼文の例は次のとおりです。
scripts/cwl-top-ingest.shを過去30日の期間で実行して、上位10のロググループをバイト数のGB換算で表にして。
GB換算はClaude側で行わせ、元のバイト数も併記させます。換算を暗算に任せると桁を誤りやすいためです。
保持期間が未設定のグループも洗い出す
順位とは別に、保持期間が無期限のままのロググループを出します。describe-log-groupsのレスポンスにはretentionInDaysとstoredBytesがあり、無期限のグループではretentionInDaysが返りません。
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==null].[logGroupName,storedBytes]' \
--output text取り込み量の上位と、無期限のグループが重なっているものから手を付けるのが近道です。
保持期間をTerraformで固定する
ロググループをaws_cloudwatch_log_groupで管理しているなら、retention_in_daysを足すだけです。
resource "aws_cloudwatch_log_group" "api" {
name = "/app/prod/api"
retention_in_days = 30
}指定できる値は、1、3、5、7、14、30、60、90、120、150、180、365、400、545、731、1096、1827、2192、2557、2922、3288、3653と、無期限を意味する0です。この一覧にない日数は指定できません。log_group_classをDELIVERYにした場合、この引数は無視され、保持期間は2日に固定されます。
複数のグループにまとめて設定する
上位5グループのように対象が複数あるなら、マップとfor_eachで1か所にまとめます。
locals {
log_retention = {
"/app/prod/api" = 30
"/app/prod/worker" = 14
"/app/stg/api" = 7
}
}
resource "aws_cloudwatch_log_group" "managed" {
for_each = local.log_retention
name = each.key
retention_in_days = each.value
}環境ごとに日数を変えられるので、ステージングは短く、本番は長めに置けます。
既存のロググループはimportが先
nameを変えるとリソースが作り直しになります。すでに存在するロググループにそのままresourceを書いてapplyすると、同名の作成でぶつかります。先にimportして状態に取り込み、planの差分がretention_in_daysだけになることを確かめてから反映します。手順はClaude CodeでTerraformのimportを進めるにまとめています。
依頼文には次の条件を入れておくと、事故が減ります。
取り込み量の上位5グループについて、
retention_in_daysを追加するHCLを書いて。planで差分が保持期間だけであることを示して。applyは実行しない。
短縮は取り消せない
落とし穴
保持期間を短くすると、古いログは戻らない
AWSのドキュメントでは、保持期間を過ぎたログイベントは削除対象としてマークされ、その後は保存料金の対象から外れます。実際の削除は通常72時間ほどかかり、まれに長引くとされています。短くした設定を後で長く戻しても、すでに削除されたログは復元できません。監査要件や調査で遡る期間がある場合は、短縮の前に必要な日数を決めておきます。
取り込み量そのものを減らす
保持期間で下がるのは保存の費用です。取り込みの費用を下げるには、送るログを減らします。
ログレベル別にどれだけ送っているか測る
DEBUGが大きな割合を占めているかを、Logs Insightsで測ります。クエリ中のparseでレベル名を取り出し、strlenとsumで文字数を合計します。
parse @message /(?<level>DEBUG|INFO|WARN|ERROR)/
| stats count(*) as events, sum(strlen(@message)) as chars by level
| sort chars desc書式は、ログの形に合わせて直します。JSONログならlevelフィールドをそのままbyに使えます。実行にはaws logs start-queryとget-query-resultsの許可が必要で、権限の切り方は先の記事と同じです。スキャン量に課金がかかるので、期間は短く取ります。
DEBUGの出力元をコードで特定する
どのロググループが重いかが分かったら、そのサービスのソースを開き、ログレベルの決まり方を調べさせます。依頼は具体的に書くほど、探索が絞れます。
/app/prod/apiのログを出しているサービスはservices/apiです。ログレベルの設定箇所を洗い出して。環境変数、設定ファイル、コード内の初期化処理をそれぞれ挙げて。本番でDEBUGになりうる経路があれば、その条件も書いて。
見つけるべきものは、たとえば次のとおりです。
- 本番の設定が環境変数の既定値に頼っていて、未設定だとDEBUGになる
- ループの内側や、リクエストごとに全体を出力する
debug呼び出しが残っている - サードパーティのライブラリが、独自の設定でDEBUGを出している
CLAUDE.mdに調べ方の型を書いておくと、毎回の依頼が短くなります。
## ログ費用の調査ルール
- ログ量はaws cloudwatch get-metric-statisticsのIncomingBytesで確認する
- 保持期間の変更はTerraformのretention_in_daysで行い、CLIで直接変更しない
- ログレベルを変える提案には、本番のレベル、変更箇所、影響を受けるアラートを添えるロググループの種類で下げる手もある
CloudWatch Logsには、標準クラスのほかにInfrequent Accessクラスがあります。AWSのドキュメントによれば、2つの違いは取り込み料金だけで、保存料金とLogs Insightsの料金は同じです。Infrequent Accessは、アドホックな検索や事後の調査に向いた、機能を絞ったクラスです。
ただし、ロググループを作成した後に、ログクラスを変更することはできません。変えるなら、新しいロググループを作って出力先を切り替えます。
機能差も大きく、Infrequent AccessではLive Tail、メトリクスフィルター、サブスクリプションフィルター、FilterLogEventsが使えません。アラームの元にしているログや、リアルタイムで追うログは標準クラスに残します。
効果を見る
変更後は、同じスクリプトを同じ期間の長さで再実行します。比較するのは2つです。
- 対象グループの
IncomingBytesの合計が、ログレベルの変更後に減ったか - 保持期間を短くしたグループの
storedBytesが減ったか
AWSのドキュメントには、削除対象としてマークされたログはstoredBytesの値に含まれないとあります。マークされた時点で数字は下がり始めます。実際にいくら下がったかは、費用の内訳を見ます。取り込み量と請求は完全には一致しないので、Claude CodeでInfracostを使い、PRのクラウド費用を下げる手順のように、変更前後を継続的に見る仕組みと組み合わせると確実です。
AWS CLIをMCP経由で動かす構成で権限を設計する場合は、aws-api-mcp-serverの権限設計が詳しい記事です。
まとめ
保持期間の見直しは、ロググループごとにTerraformで書けて、効果も早く出ます。ただし下がるのは保存の費用だけです。取り込みの費用を動かすには、DEBUGをはじめ、不要なログを出している箇所をコードから特定して、出力そのものを減らす必要があります。Claude Codeには読み取りだけを許可して順位と出力元を探させ、変更はTerraformのPRとしてplanの差分で確かめるのが、手戻りの少ない進め方です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/permissions
- docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Logs-Monitor…
- docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-s…
- docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatch_Logs_Log_Classes.h…
- docs.aws.amazon.com/cli/latest/reference/cloudwatch/get-metric-statistics.html
- registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_l…