【AWS】CloudWatch Logsで本番障害を追う|ログクエリ(Logs Insights)の実践例

はじめに

本番で障害が出たとき、まず CloudWatch Logs のコンソールでログストリームを開き、エラー文字列を目で追っていませんか。ログ量が多いとスクロールが追いつかず、「いつから増えたか」「どのリクエスト ID か」を掴むまでに時間がかかります。

CloudWatch Logs Insights は、ロググループに対してクエリを実行し、条件に合うイベントの抽出や集計を行える機能です。障害調査では、次のような問いに答えやすくなります。

  • 直近 30 分で ERROR を含むログは何件か
  • 例外メッセージの出現が時間帯でどう増えたか
  • Lambda の遅い実行(REPORT 行)はどれか
  • JSON ログから requestIdstatus を取り出して絞れるか

この記事では、Logs Insights の専用クエリ言語(Logs Insights QL)を前提に、コンソールでの実行手順と、本番障害で使いやすいクエリ例を整理します。コンソールには OpenSearch 系の PPL / SQL もありますが、本記事では扱いません。

前提: CloudWatch Logs にアプリや Lambda のログが入っていること。検証環境は AWS マネジメントコンソール上の操作を想定しています。IAM では、対象ロググループの閲覧と Logs Insights のクエリ実行が必要です。例として logs:DescribeLogGroupslogs:StartQuerylogs:GetQueryResults があります。一覧にロググループが出ない、またはクエリ実行が Denied になるときは、権限とリージョンを先に確認してください。

背景:Logs Insights で何ができるか

ロググループとクエリの関係

CloudWatch Logs では、関連するログを ロググループ にまとめ、その中の ログストリーム にイベントが時系列で並びます。Logs Insights は、選んだロググループ(複数可)と時間範囲に対してクエリを走らせ、結果を表形式で返します。

料金は、返ってきた件数ではなく スキャンした非圧縮ログデータの量 に基づきます(Analyzing log data with CloudWatch Logs Insights)。広い時間範囲や巨大なロググループを選ぶほど、調査コストと待ち時間が増えやすい点を先に押さえておきます。単価はリージョンや時期で変わるため、Amazon CloudWatch Pricing を確認してください。

自動検出フィールド(@ 付き)

Logs Insights は、多くのログ形式についてフィールドを自動検出し、@ で始まる名前を付けます(Supported logs and discovered fields)。Standard ログクラスのロググループでは、少なくとも次のシステムフィールドが付きます。

フィールド意味の目安
@timestampイベントのタイムスタンプ
@messageログ本文
@logStream所属するログストリーム名
@logロググループの識別情報(account-id:log-group-name 形式)
@ingestionTimeCloudWatch Logs が受け取った時刻

Lambda の標準ログでは、加えて @requestId@type@duration@memorySize@maxMemoryUsed などが検出されます。アプリ独自のプレーンテキストでは、まず @messagefilterparse で扱う流れが一般的です。JSON 形式ではネストしたキーもフィールドとして検出されやすいです。

クエリの基本形

コマンドはパイプ | でつなぎます。コメントは # です(CloudWatch Logs Insights language query syntax)。障害調査でよく使うコマンドは次の表です。

コマンド役割
fields表示するフィールドを選ぶ。計算や別名も可能
filter条件に合うイベントだけ残す
sort並び替え(asc / desc
limit返す件数の上限
stats件数・平均などの集計
parse@message などからフィールドを切り出す
dedup指定フィールドの重複を除く

公式ドキュメントでは、過剰な課金を避けるために次が推奨されています。

  • 必要なロググループだけを選ぶ
  • 時間範囲をできるだけ狭くする
  • コンソールを閉じる前に実行中クエリをキャンセルする(閉じても完了まで動き続ける)

実装と検証:コンソールでクエリを走らせる

1. Logs Insights を開く

  1. 障害が起きた リージョン を選ぶ
  2. CloudWatch コンソールで LogsLogs Insights を開く
  3. 対象の ロググループ を選択する(障害の疑いがあるアプリ / Lambda から)
  4. 右上の 時間範囲 を障害時刻付近に絞る(まずは 15〜60 分など短めから。時間を半分にするとスキャン対象も減りやすい)
  5. クエリエディタにクエリを貼り、クエリの実行 を押す

結果タブで行を選び、前後のログ(Surrounding logs / 前後のログ)を見る機能もあります。原因候補の 1 行が見つかったあとに、前後の文脈を確認する用途向けです。

2. まず「直近のログ」を出す

何も絞れないときは、公式サンプルと同様に最新イベントを確認します。

fields @timestamp, @message
| sort @timestamp desc
| limit 25

ここでの sql はエディタ表示用の識別子です。構文は Logs Insights QL です。

実装時の注意点:

  • limit は表示件数の上限であり、課金対象のスキャン量を直接ゼロにはしない。時間範囲とロググループの選定が主である
  • ロググループ作成より前のタイムスタンプを持つイベントは、Logs Insights から参照できないとドキュメントにある
  • 2018 年 11 月 5 日より前に送信されたログデータは、Logs Insights の検索対象外である(Analyzing log data

実践例:本番障害でよく使うクエリ

ここからは、冒頭の問いに対応するクエリ例です。公式の Sample queries をベースにしています。ログ本文の文言は環境に合わせて置き換えてください。

例1:ERROR / Exception を含む行を探す

fields @timestamp, @message, @logStream
| filter @message like /(?i)(error|exception)/
| sort @timestamp desc
| limit 50

like に正規表現を渡し、(?i) で大文字小文字を無視する書き方です(filter)。まず件数と時刻の分布を見てから、メッセージを具体化するとノイズが減ります。

例2:例外の発生件数を 1 時間ごとに集計する

件数が多い時間帯(ピーク)を探すときの定番です。

filter @message like /Exception/
| stats count(*) as exceptionCount by bin(1h)
| sort exceptionCount desc

bin(1h) は時間バケットです。sort exceptionCount desc は件数の多いバケット順です。時系列で眺めたいときは sort bin(1h) asc に変えます。障害が短い時間に集中しているなら bin(5m)bin(1m) も検討してください。

例3:Lambda の遅い実行(REPORT)を探す

Lambda のロググループ向けです。非 Lambda のアプリログでは例1・2・4を先に使ってください。Lambda は実行終了時に @type = "REPORT" の行を出します。@duration はミリ秒です。

fields @timestamp, @requestId, @message, @logStream
| filter @type = "REPORT" and @duration > 1000
| sort @timestamp desc
| dedup @requestId
| limit 20

1 秒超の実行を探し、リトライなどで同じ @requestId が重複する場合は dedup で抑えます。しきい値の 1000 はアプリの SLA に合わせて変更してください。

メモリの余裕を見る場合は、公式の REPORT 集計例が参考になります。

filter @type = "REPORT"
| stats max(@memorySize / 1000 / 1000) as provisionedMemoryMB,
        avg(@maxMemoryUsed / 1000 / 1000) as avgMemoryUsedMB,
        max(@maxMemoryUsed / 1000 / 1000) as maxMemoryUsedMB

例4:JSON ログからフィールドを取り出して絞る

アプリが JSON 1 行ログを出している場合、検出済みフィールドを使えることがあります。例として、次のようなログを想定します。

{"level":"error","requestId":"abc-123","status":500,"msg":"payment failed"}

クエリ前に、コンソールの検出フィールド一覧で levelstatus が見えることを確認してください。見えない場合は、後述の parse か例1の @message like に切り替えます。

fields @timestamp, level, requestId, status, msg
| filter level = "error" and status >= 500
| sort @timestamp desc
| limit 50

フィールド名にドットなどが含まれる場合は、バッククォートで囲むとよいです。規則の詳細は Fields that contain special characters 周辺を参照してください。

プレーンテキストから切り出すときは parse を使います。パターンは自分の @message 1 行に合わせて * の位置を調整してください。

fields @timestamp, @message
| parse @message "requestId=* status=* *" as requestId, status, rest
| filter status = "500"
| sort @timestamp desc
| limit 50

parse は glob と正規表現の両方に対応します(parse の例)。

例5:特定リクエストの流れを追う

アラートやクライアントから requestId が分かっている場合です。本文に ID が埋もれているなら like、JSON や Lambda でフィールドとして検出されているなら等価比較が向いています。

# 本文検索
fields @timestamp, @message, @logStream
| filter @message like /abc-123/
| sort @timestamp asc
| limit 100
# 検出フィールドがある場合(Lambda なら @requestId も使える)
fields @timestamp, @message, requestId
| filter requestId = "abc-123"
| sort @timestamp asc
| limit 100

複数マイクロサービスにログが分かれているときは、関連するロググループをまとめて選択し、同じ ID で横断検索します。スキャン量は増えるため、時間範囲は障害時刻に寄せてください。


調査を速くする運用上のコツ

コツ理由
時間範囲を障害時刻±αに絞るスキャン量と待ち時間を抑える
まず filter で件数を見る全文を眺める前に規模感を掴む
stats でピーク時刻を特定するいつ増えたかが分かると原因仮説が立てやすい
クエリを保存する同じ調査手順を次回に再利用できる

大規模ログを繰り返し調べる段階では、フィールドインデックスも検討できます。今すぐ必須ではなく、まずは時間範囲の絞り込みで十分なことが多いです。詳細は Create field indexes を参照してください。filterlike 条件はインデックスの恩恵を受けない、とドキュメントにあります。


よくある失敗例:時間範囲を広げすぎてクエリが重い

障害対応中に「念のため過去 7 日」で複数の巨大ロググループを選び、次のようなクエリだけを繰り返し実行するケースです。

fields @timestamp, @message
| filter @message like /error/
| sort @timestamp desc
| limit 20

limit 20 でも、選択した時間範囲のログはスキャン対象になり得ます。結果が 20 行でも、スキャン量に応じた課金と待ち時間が発生します。コンソールを閉じても実行中クエリは完了まで動き続けるため、不要ならキャンセルしてください。

対処:

  1. まず障害通知時刻の前後 30〜60 分に絞る
  2. ロググループは疑わしいものから 1〜2 個に限定する
  3. ピークが見えたら、そのバケットだけ時間をさらに狭めて詳細を見る
  4. 調査が終わったら実行中クエリを確認し、残っていればキャンセルする

もう1つの失敗は、JSON と思って status で絞ったのに結果が空になるケースです。実際の @message がプレーンテキストだと、検出フィールド status は存在しません。そのときは例1のように @message like /.../ か、parse で切り出してから絞ります。


まとめ

  • Logs Insights は、ロググループと時間範囲に対して Logs Insights QL で抽出・集計できる
  • 課金は結果件数ではなくスキャンしたログ量ベース。時間範囲とロググループの選定が調査コストに直結する
  • 障害調査では、まず絞る(filter)→ ピークを見る(stats)→ 必要なら切り出す(parse)の順が使いやすい

次に試すこと

  1. 検証用ロググループ(意図的に ERROR を出したサンプルでも可)で、例1の ERROR 検索を 15 分の時間範囲で実行する
  2. 例2で例外件数のピークを出し、その前後だけ時間範囲を狭めて再実行する
  3. Lambda を使っているなら、例3で @duration のしきい値を自分の SLA に合わせて変える
  4. よく使うクエリを Logs Insights の保存クエリに登録する
  5. 料金感を見たい場合は Amazon CloudWatch Pricing で Logs Insights のスキャン課金を確認する

Source

AWSAWS,Lambda

Posted by 千原 耕司