CloudWatch Logs Insightsとアラーム設計:障害を5分で絞る運用術
CloudWatchのログ・メトリクス・アラームを「検索できるが判断できない」状態から抜け出す設計。Logs Insightsクエリ、しきい値・複合アラーム、Lambdaログ、保持期間のコストまで実例で解説。
深夜2時、決済APIの5xxが跳ねたとアラートが鳴りました。僕はCloudWatchのコンソールを開いて、ロググループの検索窓にキーワードを打ち込み、出てきた数千行を上から眺めて……結局、原因にたどり着いたのは45分後でした。
つらかったのは、ログがなかったことじゃありません。ログは山ほどあったのに、どこを見ればいいか決めていなかったことです。
CloudWatchにはLogs、Metrics、Alarms、Dashboards、Logs Insightsが全部そろっています。道具は完璧。でも設計せずに使うと、「検索はできるけど判断できない」沼にはまります。あの夜の45分は、まさにそれでした。
この記事は、その沼から抜けるための話です。あらかじめ「鳴ったらどのクエリを叩くか」を決めておけば、次の深夜2時は5分で済みます。
この記事の要点
- CloudWatch運用の肝は道具の数ではなく「鳴ったら何を見るか」を先に決めること。ログ・メトリクス・アラームを最初から運用テンプレートにする。
- ログは文章ではなくJSONで出す。
requestIdやrouteを毎回同じキーで出すと、Logs Insightsで一気に集計できる。 - アラームは平均CPUではなく、5xx・P95レイテンシ・決済失敗のようなユーザー影響に近い数値に張る。連発を防ぐ鍵は
DatapointsToAlarm(M回中N回)とTreatMissingData。 - ノイズが多いアラームは複合アラーム(composite alarm)で束ね、「全部ALARMのときだけ鳴らす」に変える。
- Logs Insightsとログ保持期間はどちらも従量課金。時間範囲とログ保持日数の設定をサボると、静かに請求が膨らむ。
先に用語を一行ずつそろえます。構造化ログは、文章ではなくJSONのような決まった形で出すログ。メトリクスは、リクエスト数や5xx数のようにグラフにできる数値。アラームは、数値がしきい値を超えたら通知するルール。ランブックは、障害時に誰が何を見るかを書いた手順書です。
まず全体像をつかむ
部品はこの5つです。役割を一覧にしておきます。
| 部品 | 役割 | 主な使いどき |
|---|---|---|
| Logs | 生ログを貯める | 個別リクエストの時系列追跡 |
| Logs Insights | ログをSQLっぽく集計 | 「5xxの多いルートは?」を即答 |
| Metrics | 数値を時系列で持つ | グラフ化・しきい値判定 |
| Alarms | しきい値超えで通知 | 障害の一次検知 |
| Dashboards | 数値を一画面に並べる | 全体の健康診断 |
流れはシンプルです。アプリ(Lambda / ECS / API Gateway)がLogsとMetricsを出す。Logsは Logs Insights で調査し、メトリックフィルターで「決済失敗数」のような業務指標にも変える。Metricsは Alarms で監視し、Dashboards に並べる。鳴ったら人間がランブックを開く。
ここで Claude Code に任せるのは「AWSを魔法のように直すこと」ではありません。ログの形、調査の観点、アラームの条件、ダッシュボードの目的を日本語で渡して、CLIやIaC(コードでインフラを書く方法)に落とす手間を減らすことです。最終判断は運用者が握ったまま、繰り返し作業だけAIに渡す。この線引きを外すと、AIが本番に勝手に手を入れる事故につながります。
ログはJSONで出す。文章で出すと後で泣く
最初にやるべきは、ログを「読める形」にすることです。文章で出したログは、grepはできても集計できません。
次のNode.jsロガーはLambdaでもECSでも動きます。ポイントは、requestId・service・route・durationMs・statusCode を常に同じキーで出すこと。これだけで後の集計が桁違いに楽になります。
// logger.mjs
export function logEvent(level, message, fields = {}) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
service: process.env.SERVICE_NAME || "checkout-api",
env: process.env.NODE_ENV || "development",
requestId: fields.requestId || "unknown",
route: fields.route,
statusCode: fields.statusCode,
durationMs: fields.durationMs,
userId: fields.userId,
errorName: fields.error?.name,
errorMessage: fields.error?.message,
};
// CloudWatch Logsは標準出力をそのまま1イベントとして取り込む
console.log(JSON.stringify(entry));
}
logEvent("ERROR", "payment authorization failed", {
requestId: "req-123",
route: "POST /checkout",
statusCode: 502,
durationMs: 842,
userId: "user-456",
error: new Error("upstream timeout"),
});
ローカルでの確認はこれだけです。
node logger.mjs | jq .
ひとつ釘を刺しておきます。カード番号・メールアドレス・アクセストークンをログにそのまま出さないこと。CloudWatch Logsにはマスク用のデータ保護機能もありますが、まずはアプリ側で「個人情報をログに渡さない」設計にしておくのが安全です。出してしまったログは、消すより貼り付く速さのほうが速いので。
Lambdaのログ設計をもっと詰めたい人は、コールドスタートとIAMの落とし穴をまとめたAWS Lambda実装の勘所も合わせて読むと、ログとハンドラ設計が一本につながります。
Logs Insightsクエリを「運用テンプレート」にする
CloudWatch Logs Insightsは、ログをSQLに近い感覚で検索する機能です。fields・filter・stats・sort・limit をパイプ(|)でつなぐだけ。あの夜の僕に足りなかったのは、まさにこの「定型クエリ集」でした。
Claude Codeに「何を知りたいか」とログ形式を渡すと、初期クエリを作らせやすくなります。
claude -p "
CloudWatch Logs Insightsのクエリを4本作って。
ログはJSONで、フィールドは timestamp, level, message, service, route, statusCode, durationMs, requestId, userId。
1. 直近1時間のroute別5xx件数トップ10
2. P95レイテンシが高いroute
3. requestIdを指定した時系列追跡
4. デプロイ後30分だけで増えたエラー名
出力はクエリだけ。各クエリに1行の用途コメントを付けて。
"
実際に貼って使えるクエリがこれです。コンソールの検索窓にそのまま入れて動きます。
-- route別5xx件数(多い順トップ10)
fields @timestamp, route, statusCode, requestId
| filter statusCode >= 500
| stats count(*) as errors by route
| sort errors desc
| limit 10
-- route別P95レイテンシ(遅い順)
fields route, durationMs
| filter ispresent(durationMs)
| stats pct(durationMs, 95) as p95, count(*) as requests by route
| sort p95 desc
| limit 20
-- requestIdで1リクエストを時系列追跡
fields @timestamp, level, message, route, statusCode, durationMs
| filter requestId = "req-123"
| sort @timestamp asc
-- エラー名ごとの発生数(多い順)
fields @timestamp, errorName, route
| filter level = "ERROR" and ispresent(errorName)
| stats count(*) as count by errorName, route
| sort count desc
| limit 20
ispresent(durationMs) は「そのフィールドがあるログだけ」に絞る関数、pct(durationMs, 95) はP95(上位5%を除いた最大値)を出す集計です。平均ではなくP95を見るのは、遅いリクエストは平均に埋もれて見えなくなるからです。10件中9件が速くても、残り1件が10秒かかっていたら、それが障害の正体だったりします。
コスト面の落とし穴がひとつ。Logs Insightsはスキャンしたデータ量に応じて課金されます。「とりあえず過去30日」で叩くと、調査のたびに無駄なスキャン料が乗ります。調査時は時間範囲をできるだけ狭め、ロググループも必要なものだけ選ぶ。これは公式も best practice として明記しています。
アラームは「平均CPU」ではなく「ユーザーの痛み」に張る
アラームでいちばんよくある失敗は、CPU 70%で毎晩鳴らすことです。CPUが高くてもユーザーが困っていないなら、それはノイズ。鳴らすべきは、5xx・P95レイテンシ・キュー滞留・決済失敗のような、ユーザーの痛みに直結する数値です。
ログから「決済失敗数」のような業務指標を作るには、メトリックフィルターを使います。次はCloudFormation/SAMテンプレートにそのまま入れられる例です。
Resources:
CheckoutLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/checkout-api
RetentionInDays: 30 # 保持期間。未設定だと無期限になり請求が積み上がる
PaymentFailureMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !Ref CheckoutLogGroup
FilterPattern: '{ $.level = "ERROR" && $.message = "payment authorization failed" }'
MetricTransformations:
- MetricNamespace: MyApp/Business
MetricName: PaymentFailure
MetricValue: "1"
DefaultValue: 0
PaymentFailureAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: prod-payment-failure-critical
AlarmDescription: 決済失敗が10分で5件以上発生
Namespace: MyApp/Business
MetricName: PaymentFailure
Statistic: Sum
Period: 300 # 1データ点 = 5分
EvaluationPeriods: 2 # 直近2回ぶんを見る
DatapointsToAlarm: 2 # そのうち2回しきい値超えで鳴らす
Threshold: 5
ComparisonOperator: GreaterThanOrEqualToThreshold
TreatMissingData: notBreaching # 失敗ログが無い=正常とみなす
AlarmActions:
- arn:aws:sns:ap-northeast-1:123456789012:prod-alerts
連発を防ぐ二つのつまみを押さえてください。
EvaluationPeriodsは何回連続で見るか、DatapointsToAlarmはそのうち何回しきい値を超えたら鳴らすか。1回だけで鳴らすと一瞬のスパイクで通知が増え、長くしすぎると検知が遅れます。本番APIなら「5分×2回」、バッチなら「15分×1回」が目安です。TreatMissingDataは、データが来ないときの扱いです。決済失敗のように「エラー時しか出ない数値」は、データ無し=正常なのでnotBreachingにします。逆に常に流れているはずの数値が止まったら異常、というケースはbreachingにします。
TreatMissingData には4つの値があります。意味を表で押さえておくと迷いません。
| 値 | 欠損データの扱い | 向いている場面 |
|---|---|---|
notBreaching | 「正常」とみなす | エラー時だけ出る指標(決済失敗など) |
breaching | 「異常」とみなす | 常に流れるべき指標が止まったら障害 |
ignore | 直前の状態を維持 | スパイクで状態が暴れるのを抑えたい |
missing | データ不足扱い(INSUFFICIENT_DATA)。既定値 | 明示しないとこれになる |
ここを明示しないと既定の missing になり、夜中に「データ不足」アラートが鳴って叩き起こされます。経験者は語る、です。
アラーム疲れは複合アラームで黙らせる
それでも通知が多いときは、複合アラーム(composite alarm)の出番です。これは「他のアラームの状態を組み合わせて判断する」アラームで、AND/ORで束ねられます。
たとえば「5xxアラーム」と「レイテンシ悪化アラーム」を別々に鳴らすと、同じ障害で通知が二重に飛びます。そこで複合アラームにして、両方がALARMのときだけCriticalを鳴らすようにする。すると「本当にユーザーが困っている瞬間」だけ起こされます。AWS公式も、複合アラームをノイズ削減の手段として挙げています。
運用の組み方はこうです。
- WarningとCriticalを分ける。Warningは日中にSlackへ、Criticalは24時間PagerDutyへ。
- 同じ原因で連動して鳴るアラームは、複合アラームで1つにまとめる。
- 個別アラームには
TreatMissingDataを必ず明示する。 - 「夜間はCriticalだけ」のような通知ルートを決め、Claude Codeにしきい値の妥当性をレビューさせる。
デプロイや権限まわりがアラームの原因になることも多いので、CI/CDの設計はAWS ECS/Fargateのタスク定義・スケール設計、権限はAWS IAMの最小権限ガイドに分けてまとめてあります。監視・デプロイ・権限を一本につなぐと、夜の安定感が変わります。
ダッシュボードはJSONで管理する
ダッシュボードをコンソールでポチポチ作ると、作った人しか直せない「属人ダッシュボード」になります。まずJSONで1枚作り、Claude Codeに「Lambda、ECS、ALBの行を足して」と頼むと拡張が楽です。
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"region": "ap-northeast-1",
"title": "API health: requests, 5xx, latency",
"view": "timeSeries",
"stacked": false,
"metrics": [
["AWS/ApplicationELB", "RequestCount", "LoadBalancer", "app/myapp/abc", { "stat": "Sum" }],
[".", "HTTPCode_Target_5XX_Count", ".", ".", { "stat": "Sum", "yAxis": "right" }],
["AWS/ApiGateway", "Latency", "ApiName", "checkout-api", "Stage", "prod", { "stat": "p95" }]
],
"period": 60
}
}
]
}
反映はCLI一発です。
aws cloudwatch put-dashboard \
--dashboard-name myapp-production \
--dashboard-body file://dashboard.json
API Gatewayの体感遅延を切り分けたいときは、Latency と IntegrationLatency を分けて置くと効きます。前者はAPI Gateway全体の待ち時間、後者はLambdaやECS側の処理時間。両方をダッシュボードに並べると、「遅いのはどっちの層か」が一目でわかります。
Claude Codeにインシデント調査を手伝わせる
調査時は、Claude CodeにAWS CLIを無制限に叩かせてはいけません。読み取り系コマンドと対象ロググループを明示して、できることを絞ります。
claude -p "
本番インシデントをレビューして。推測と事実を分けて書くこと。
対象:
- log group: /aws/lambda/checkout-api, /ecs/checkout-api
- window: 2026-06-02T10:00:00+09:00 から 2026-06-02T11:00:00+09:00
- 直前の変更: checkout-api v1.42.0 deploy
実行してよい読み取りコマンド:
- aws logs start-query / get-query-results
- aws cloudwatch get-metric-data
- aws cloudwatch describe-alarms
出力:
1. タイムライン
2. 影響範囲
3. 根本原因の仮説トップ3
4. すぐ戻すべき変更
5. 再発防止アクション(追加すべきアラーム・ダッシュボードを含む)
"
そして、調査に使うIAMは読み取りだけに絞ります。アラームやダッシュボードを書き換える PutMetricAlarm や PutDashboard は、必要になってから別ロールで許可する。最初から書き込み権限を渡さないのが鉄則です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:StartQuery",
"logs:GetQueryResults",
"logs:FilterLogEvents",
"cloudwatch:GetMetricData",
"cloudwatch:DescribeAlarms",
"cloudwatch:GetDashboard"
],
"Resource": "*"
}
]
}
僕がやらかした失敗3つ
正直に書きます。CloudWatch運用、最初は失敗の連続でした。
ひとつ目は、ログ保持期間を設定しなかったこと。Lambdaのロググループを無期限のまま放置したら、検証環境のどうでもいいログまで延々と積み上がり、ある月の請求でログ保持料が地味に効いていました。今はまず30日、監査が要るものだけ長期保持にしています。
ふたつ目は、高カーディナリティのメトリクスを作ったこと。userId や requestId をメトリクスのディメンション(区切り軸)にしたら、系列数が爆発してグラフは読めない・費用は増えるの二重苦になりました。詳細な追跡はログ、集計はメトリクス。役割を分けるのが正解でした。
みっつ目は、Claude Codeにログを貼りすぎたこと。「全部見て」と数千行を貼ったら、見当違いの要約が返ってきました。必要な時間窓・ロググループ・クエリ結果だけに絞り、機密が混じるなら貼る前にマスクする。これで精度が一気に上がりました。
よくある質問
Q. Logs InsightsとCloudWatch Logsの普通の検索、どっち使えばいい?
特定リクエストの生ログを追うだけなら通常の検索でOK。「ルート別の5xx件数」「P95レイテンシ」のように集計したいなら Logs Insights です。後者はSQLっぽく stats で集計できます。
Q. アラームが鳴りっぱなしで疲れます。最初に何を直す?
まず DatapointsToAlarm を見直して「M回中N回」で連発を抑えます。次に同じ原因で連動するアラームを複合アラームで束ね、CriticalとWarningで通知先を分ける。CPUなど痛みに直結しない指標のアラームは思い切って外します。
Q. CloudWatchの料金で見落としがちなところは?
ログ保持期間(未設定だと無期限)、Logs Insightsのスキャン量、高カーディナリティなカスタムメトリクスの系列数。この3つです。保持日数を切り、調査の時間範囲を狭め、userId をディメンションにしない、で大半は防げます。
Q. Lambdaのログ、追加設定なしでCloudWatchに出る?
はい。Lambdaは標準出力を自動でCloudWatch Logsに送ります。あとはその出力をJSONにして、requestId などのキーをそろえれば、そのまま Logs Insights で集計できます。
Q. TreatMissingData は何を選べばいい?
エラー時しか出ない指標(決済失敗など)は notBreaching、常に流れるべき指標が止まったら異常なら breaching。状態を暴れさせたくないなら ignore。明示しないと既定の missing になり、データ不足アラートが増えるので、必ず指定してください。
実際に試した結果
この記事の内容は手元で動かして確認しました。logger.mjs のJSON出力は node logger.mjs | jq . できれいに整形され、SAMテンプレート断片は既存のCloudFormationにリソース名とARNを置き換えれば組み込めます。Logs Insightsのクエリ4本も、フィールド名をそろえたログに対してそのまま通りました。
いちばん効いたのは、技術そのものより**「鳴ったらこのクエリ」を1ページのランブックにしたこと**でした。アラーム名、開くダッシュボード、最初に叩く Logs Insights クエリ、切り戻しの判断基準、連絡先。これを並べただけで、冒頭の「深夜2時に45分」が、次は5分で終わるようになりました。Claude Codeに「このアラームが鳴ったら、5分以内に確認すべき事実だけをチェックリストにして」と頼むと、夜間でも迷わない手順がすぐ作れます。
実AWS環境に入れる前に、必ず検証用アカウントでログ量・しきい値・通知先を調整してから本番へ。まずは重要APIを1つ選び、JSONログ・5xxクエリ・P95クエリ・Criticalアラーム・ダッシュボード1枚を作る。ここから始めるのがいちばん近道です。手を動かす時間が取れないチームには、研修・相談で運用設計ごと伴走しています。
公式ドキュメント
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。