AWS
CloudWatch Logsの調査で件数と時刻を取り違えない—1万行の打ち切り・UTC表記・0件の切り分け
CloudWatch Logs InsightsやアラームAPIの結果を数えて結論を出す前に、気づかないうちに切れる上限、APIごとに違う時刻の形式、0件の原因を確かめる判断基準と、AIエージェントに調査させるときの貼り付け文。
CloudWatch Logsで障害やバッチの調査をしていると、件数が1万前後で止まる、時刻が9時間ずれる、検索が0件になる、といった結果に出会います。どれもエラーは出ません。そのまま報告すると、起きた時刻・件数・原因の説明が間違ったまま共有されます。AIエージェントに調査させると、さらに気づきにくくなります。数字を根拠にした説明が、早く、もっともらしく出てくるからです。読み終わると、CloudWatchの結果を数える前に確かめる項目と、調査の最初にAIへ渡す指示がそろった状態になります。
この記事でわかること
気づかないうちに切れる件数の上限、APIごとに違う時刻の形式、0件や件数差の原因の切り分けを、実際に踏んだ失敗ごとに合格条件つきで示します。最後に、AIエージェントに調査を任せるときのルール文と開始時の貼り付け文をまとめます。
前提
2026年10月時点の実例です。Windows PowerShell 5.1とAWS CLI v2で集計ツールを作っていた現場で起きました。調査にはCloudWatch Logs Insightsとアラーム履歴を使っていました。上限値や返り値の形式は変わることがあるので、AWSの公式ドキュメントを優先してください。
失敗1 Logs Insightsの結果が1万行で切れているのに気づかない
エラーの一覧をLogs Insightsで出し、その行数を「エラー件数」として報告する場面です。
悪い例では、結果がいつも10000行前後で止まっているのに、そのまま件数として使います。Logs Insightsが返す結果の行は1万行が上限です。コンソールなら条件に合った件数と表示件数が並んで出ますが、APIで受けて行数だけを数えるツールやエージェントは、切れたことに気づきません。さらに、その一覧から「再試行で成功した分」を引き算して派生の件数を出すと、分母がすでに欠けているので比率も狂います。
良い例では、件数は stats count(*) の集計から取り、一覧は hits >= N のような条件で1万行に収まるまで絞ってから見ます。派生の件数を出すときも、分母は全量の集計を使います。
合格条件は、報告する件数がすべて集計クエリの値で、一覧の行数から数えたものではないことです。
失敗2 アラーム履歴を最初のページだけで数える
アラームの通知メールは何十通も来ているのに、ツールが数えた発報回数が少なく出た例です。
原因は、アラーム履歴(describe-alarm-history)を最初の1ページ、約100件分しか取っていなかったことでした。この現場では夜間に出る INSUFFICIENT_DATA への遷移が履歴を埋めていて、1ページ目には直近の数時間分しか入っていませんでした。
直すには、次ページのトークン(NextToken)が返らなくなるまで取ってから数えます。発報の回数は、ALARM に入って OK に戻った組を1回として数えます。状態遷移の行をそのまま数えると、INSUFFICIENT_DATA への遷移や OK への戻りまで発報に混ざります。
合格条件は、ページのあるAPIを最後のページまで取っていることを、取得したページ数としてログに出していることです。
失敗3 filter-log-eventsで取った1日分を全量とみなす
ログを手元に落として分析するために、filter-log-events で1日分を取得した場面です。Logs Insightsで数えた件数より少なく、同じ範囲で取り直しても埋まりませんでした。
実例では、同じ時刻にまとめて届いたログのひとかたまりが、丸ごと返ってこない時間帯がありました。取得が正常終了しても、件数が合っている保証にはなりません。
対策として、Logs Insightsで1時間ごとの件数を出し、手元の件数と時間ごとに比べます。足りない時間だけ範囲を狭めて取り直し、eventId で重複を除いてから足します。
合格条件は、手元に落としたログの件数が、時間ごとにLogs Insightsの集計と一致していることです。
失敗4 時刻の形式がAPIごとに違うのに、1つの前提で読む
CloudWatchの中でも、時刻の返し方は場所ごとに違います。実際に踏んだものを並べます。
| 場所 | 返り方 | 起きたこと |
|---|---|---|
Logs Insightsの @timestamp、bin、min、max | オフセットなしのUTC | PowerShellで日時に変換するとローカル時刻扱いになり、日中の範囲で取ったのに深夜の時刻が表示された |
| アラーム履歴 | +00:00 付き | こちらだけ正しく変換され、Insightsの時刻と食い違った |
| アラームの通知メール | UTC | 00:09:00 を深夜バッチの失敗と読んだが、実際はJSTの9時9分で朝の処理の直後だった |
| ログのエクスポートの範囲指定 | エポックミリ秒 | コンソールの表示がJSTでも、JSTの日付のつもりで値を作ると9時間ずれた |
Logs Insightsの bin(1d) | UTCの日で区切る | 日単位の集計がJSTで持っている集計表と合わなかった |
悪い例は、画面に出ている時刻をそのままJSTだと思い、メモに時刻だけを書くことです。避けるには、扱う時刻ごとにオフセットの有無を確かめ、オフセットなしのUTCは明示的にUTCとして解釈してからJSTに直します。PowerShellなら次の形です。
$utc = [DateTime]::SpecifyKind([DateTime]::Parse('2026-06-25 00:35:00.000'), 'Utc')
$jst = [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($utc, 'Tokyo Standard Time')
日単位の集計は bin(1h) で出してからJSTの日に寄せます。エクスポートの範囲は、JSTの8月14日0時ならUTCの8月13日15時に当たるエポックミリ秒を入れます。
合格条件は、調査メモと画面に出す時刻すべてに (JST) か (UTC) が付いていることです。
失敗5 0件や件数差を見て、すぐ原因を書く
検索が0件だったので「そのイベントは起きていない」と書いた例と、2つのシステムの件数差を「日付の境界がずれているせい」と説明した例です。どちらも誤りでした。
0件には少なくとも3つの原因があります。1つ目はクエリ・ロググループ・時間範囲の誤りです。Logs Insightsでは、クエリの中で @timestamp >= "2026-06-25 00:35:00" のように日時の文字列と比べても意図どおりに絞れず、エラーか0件になります。期間は画面の時間範囲かAPIの開始・終了時刻、クエリ内ならエポックミリ秒か ago() で指定します。2つ目は本当にデータが無い場合で、3つ目は対象のサービス自体が起動に失敗している場合です。実例では、参照するシークレットが無効化されていて、起動のたびに処理が中断していました。業務のログが出ないので0件になります。
件数差の例では、先に会社やテナント、環境ごとに分けて数えたところ、差はすべて別テナントのデータの混在で説明がつきました。
良い例では、本番の集計の前に fields @message | limit 3 と stats count() でクエリが動くことと、実際のログの形を確かめます。0件なら、対象サービスのエラーや警告のログ(起動時の例外、設定の読み込み失敗など)が出ていないかを先に見ます。GCPのログなら severity >= WARNING で絞れます。件数差なら、タイムゾーンを疑う前に集計の単位(テナント・環境)をそろえます。成功時のログにIDを出さない実装もあるので、IDで0件なら関数名と時間帯、開始と終了のメッセージで探し直します。
合格条件は、0件や件数差を報告するときに、ここに挙げた原因のどれを確かめて除いたかが書いてあることです。
失敗6 ログを取り込むツールの完了を、ファイルがあるかどうかで判定する
CloudWatchのログや、S3に出力したログファイルを手元に保管するツールで、「手元に同じ名前のファイルがあれば取得済み」と判定していた例です。S3側でログファイルの不具合を直したのに、手元には直す前のファイルが40件以上残りました。中身が欠けていても、名前さえあれば取得済みとして扱われます。
ファイルは名前だけでなくサイズと更新日時で新しさを見ます。CloudWatchから取るものは件数で判定します。ロググループごとにLogs Insightsの stats count(*) by @log と手元の行数を比べ、一致・不一致・未確認を画面とログの両方に出します。保持期間が切れてCloudWatch側から消えたものは「0件」と分けて表示します。この区別が無いと、利用者は「取れていない」のか「もう無い」のかを判断できません。
合格条件は、ツールの完了表示が件数の照合結果で、ロググループごとに一致・不一致・未確認・保持期間切れのどれかが出ていることです。
失敗7 動作を確かめたクエリをAIエージェントが書き直して壊す
利用者が画面で動作を確かめたLogs Insightsのクエリを渡し、集計ツールに組み込んでもらった場面です。AIエージェントは構文を「きれいに」整え、関数を別の書き方に置き換えて、クエリを動かなくしました。3回指摘しても、別の作業のついでに同じ書き換えを繰り返しました。
Logs Insightsのクエリ言語は、SQLや他のログ検索の感覚で書くと通らない書き方があります。たとえば bin は by bin(1h) の形で使い、bin(@timestamp, 1h) とは書きません。エージェントは一般的な書き方に寄せようとするので、動くクエリほど書き換えの対象になります。
良い例では、動作を確かめたクエリを文書に置いて変更禁止と書き、新しい形を試すときは別名で並べます。エージェントが調査で間違えた書き方は、時系列で別の文書に残し、次の調査の最初に読ませます。
合格条件は、動作確認済みのクエリが変更禁止の印つきで文書にあり、エージェントへのルールにそれを書き換えない一文があることです。
作業前にAIへ渡す入力
人間向けの長い手順は書きません。次はすべて、作業前にAIに読ませる文と貼る文です。
AI向けの永続ルール(rulesやAGENTS.mdに書く)
CloudWatch Logsの調査では、結果の件数と時刻を結論に使う前に次を確かめる。
- Logs Insightsが返す行は1万行が上限で、行数だけ数えると切れたことに気づけない。件数は stats の集計から取り、一覧の行数を件数として報告しない。
- アラーム履歴などページのあるAPIは、NextToken が返らなくなるまで取ってから数える。取得したページ数をログに出す。
- Logs Insightsの @timestamp・bin・min・max はオフセットなしのUTC。アラーム履歴は +00:00 付き、通知メールもUTC。JSTに直し、時刻には必ず (JST) か (UTC) を付ける。
- 日単位の集計を bin(1d) で出さない(UTCの日で切れる)。bin(1h) で出してJSTの日に寄せる。
- クエリの中で日時の文字列と比べて絞らない。期間は時間範囲の指定、開始・終了時刻、エポックミリ秒、ago() のどれかで渡す。
- 実ログの形を推測して filter や parse を書かない。先に fields @message | limit 3 で見る。
- 0件は「クエリ・ロググループ・時間範囲の誤り」「本当にデータが無い」「対象サービスが起動に失敗している」を切り分けてから報告する。件数差は、タイムゾーンを疑う前にテナント・環境ごとに分けて数える。
- 利用者が動作を確かめたクエリは書き換えない。別の形を試すときは別名で並べる。
- 原因が推測なら推測と書く。1件ずつ突き合わせて確かめるまで断定しない。
AI向けの開始時に貼る文
これからCloudWatch Logsで(調べる事象)を調べる。
最初に、使うロググループ、時間範囲(JSTとUTCの両方)、クエリを示せ。
本番の集計の前に limit 3 と stats count() でクエリが動くことを確かめ、結果を見せよ。
動作確認済みのクエリは次のとおり。書き換えるな。
(クエリを貼る)
件数が1万前後で止まっている、ページの途中で止まっている、時刻のオフセットが不明、のどれかに当たるときは、結論の前にそう報告せよ。
人間向け
なし。上をAIに渡すだけでよい。
参考リンク
- 関連:AIエージェントのよかれが事故になるとき(タイムゾーンは人間が決めた正を置く)
- CloudWatch Logs Insightsのクエリ構文(AWS公式)
- DescribeAlarmHistory(AWS公式APIリファレンス)