Claude Media
Claude CodeでAthenaのALBログを集計 — パーティション射影まで任せる

Claude CodeでAthenaのALBログを集計 — パーティション射影まで任せる

S3に溜まったALBアクセスログを、AthenaのCREATE TABLEとパーティション射影で集計し、5xxの多いURLを出す手順です。DDLの作成と実行をClaude Codeに任せるときの権限と注意点も示します。

ALBのアクセスログをAthenaで集計する作業は、最初の壁がCREATE TABLEです。34列のテーブル定義と長い正規表現を手で書くと、写し間違いが起きやすい作業です。ここをClaude Codeに任せ、続けてパーティション射影で読む範囲を日付に絞り、5xxを返したURLの上位を出すまでを順に示します。

ALBログの集計で、Claude Codeに任せる範囲

ALBのアクセスログは、S3に圧縮ファイル(.gz)で保存されます。Athenaで読むには、正規表現(RegexSerDe)でログ1行を列に分けるテーブルを作る必要があります。

分担は次のとおりです。

分担

作業の分担

  • Claude Codeに任せる

    DDLの組み立て、集計クエリの作成、結果の読み取りと要約。

  • 人が決める

    どのバケット・リージョン・期間を読むか、どのワークグループで実行するか、実行を許す操作の範囲。

右側の項目を決めないまま「ALBのログを集計して」と頼むと、DDLの土台が曖昧になります。ALBのログ項目は後から増える前提で設計されているため、列定義はClaudeの記憶でなく、公式ドキュメントの例をそのまま土台にさせます。

前提:ログの置き場所を確認する

Athenaより前に、ログがどこにどう置かれるかを押さえておきます。

  • アクセスログ機能は既定では無効で、有効にするとS3バケットに圧縮ファイルで保存されます
  • ログファイルはロードバランサーのノードごとに5分間隔で出力されます
  • 配信は結果整合性で、同じ期間のログが複数ファイルに分かれて届くことがあります
  • S3上のキーは AWSLogs/アカウントID/elasticloadbalancing/リージョン/yyyy/mm/dd/ の下に日付別で並びます

最後の日付ディレクトリ構造が、後述するパーティション射影の前提です。S3への保存先はリージョンごとに分かれるので、複数リージョンのALBを見るなら、テーブルもリージョンごとに作ります。

また、ALBのログにはCloudWatch Logsなどへの配信を選べる新しい方式があり、従来のS3ログは「レガシー」と位置づけられています。S3のログを読む本記事の手順は、従来方式を使っている環境向けです。

パーティション射影のCREATE TABLEを書かせる

ALBのログは保存先の構造が決まっているので、Athenaのパーティション射影が使えます。パーティション射影は、パーティションの値と場所をテーブルのプロパティから計算する機能です。ALTER TABLE ADD PARTITION で日付を登録する作業が要らなくなります。

Claude Codeへの依頼は、次のように範囲と禁止事項を先に書きます。

ALBアクセスログ用のAthenaテーブルを作るDDLを書いてください。
- 土台は、AWS公式ドキュメント「Create the table for ALB access
  logs in Athena using partition projection」のDDL
- 列とinput.regexはAWSドキュメントの例から変えない(末尾の ?( .*)? も残す)
- LOCATIONとstorage.location.templateは、
  s3://my-alb-logs/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/
- projection.day.rangeの開始日は 2026/10/01
- DDLはファイルに書くだけ。実行はしない

例えば次のような形になります(公式の例に沿った形。列は一部省略しています)。

CREATE EXTERNAL TABLE IF NOT EXISTS alb_access_logs (
  type string,
  time string,
  elb string,
  client_ip string,
  -- 中略: client_port ... conn_trace_id までドキュメントの例と同じ
  elb_status_code int,
  target_status_code string,
  request_verb string,
  request_url string,
  conn_trace_id string
)
PARTITIONED BY (day STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '1',
  'input.regex' = '<ドキュメントの例と同じ正規表現>'
)
LOCATION 's3://my-alb-logs/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/'
TBLPROPERTIES (
  "projection.enabled" = "true",
  "projection.day.type" = "date",
  "projection.day.range" = "2026/10/01,NOW",
  "projection.day.format" = "yyyy/MM/dd",
  "projection.day.interval" = "1",
  "projection.day.interval.unit" = "DAYS",
  "storage.location.template" = "s3://my-alb-logs/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/${day}"
)

実際にファイルへ書かせるときは、... や「中略」を使わず、列とinput.regexを全部入れるよう頼みます。生成後に確認するのは次の3点です。

  1. 列数と順序がドキュメントの例と一致している
  2. projection.day.format が yyyy/MM/dd で、S3のキーの並びと同じ
  3. storage.location.template の末尾が ${day} になっている

正規表現の末尾 ?( .*)? は、将来ALBのログ項目が増えたときに備えるための記述で、AWSのドキュメントも残すよう勧めています。Claudeが「不要な部分」と見て削ってしまうことがあるので、依頼文に書いておくと外れません。

5xxの多いURLを出すクエリ

テーブルができたら、集計クエリです。公式の例には WHERE elb_status_code >= 500 という5xxの抽出があります。これを日付で絞り、URL別に数える形へ組み替えます。

SELECT
  request_url,
  elb_status_code,
  count(*) AS hits
FROM alb_access_logs
WHERE day BETWEEN '2026/10/20' AND '2026/10/26'
  AND elb_status_code >= 500
GROUP BY request_url, elb_status_code
ORDER BY hits DESC
LIMIT 20;

day 条件を必ず付けるのが要点です。パーティション射影を使っても、読む範囲を絞るのはクエリ側の条件で、day を指定したクエリだけが該当日のパーティションに絞られます。依頼文にも「WHEREにdayを必ず入れる」と書きます。

ELB由来の5xxとターゲット由来の5xxを分ける

elb_status_code は、ロードバランサー自身が返したステータスです。アプリケーション(ターゲット)が返したステータスは target_status_code に入ります。後者は string 型の列で、ターゲットに接続できなかった場合は - になります。

そのため、障害の切り分けでは次のように条件を変えます。

見たいもの条件
ALBが返した5xx条件elb_status_code >= 500
アプリが返した5xx条件target_status_code LIKE '5%'
ターゲットに届かなかったリクエスト条件target_status_code = '-'

target_status_code >= 500 と書くと、文字列と数値の比較になって失敗するか、意図しない結果になります。Claudeに頼むときは、列の型を踏まえて書くよう一言添えます。

URLがばらけるときは集約を頼む

request_url をそのまま GROUP BY すると、クエリ文字列や記事IDの違いで別のURLとして数えられ、上位が見えにくくなります。その場合は、Claudeに「パス部分だけで集約し、数値のIDは {id} に置き換える」クエリへ書き換えさせます。置き換えの規則は業務ごとに違うので、規則を依頼文に書くか、数件のURLを例として渡します。

また、AWS WAFが拒否したリクエストは、既定では elb_status_code に403として記録されます。5xxを数えるときは影響しませんが、4xxの急増を見るときは、WAFによるブロックが混じることを頭に入れておきます。

実行までClaude Codeに任せる

DDLとクエリが決まったら、実行と結果の読み取りも任せられます。非対話モード(claude -p)は、--allowedTools で使えるツールを事前に許可できます。

claude -p "alb_queries/top5xx.sql を実行し、結果を要約して" \
  --allowedTools "Read" \
  "Bash(aws athena start-query-execution *)" \
  "Bash(aws athena get-query-execution *)" \
  "Bash(aws athena get-query-results *)"

許可ルールのポイントが2つあります。

  • Bash(aws athena *) のようにまとめず、使うサブコマンドだけを並べる。ワイルドカードは、サブコマンドの後ろに置くのがClaude Codeの権限ルールの書き方
  • 許可ルールを絞っても、start-query-execution に渡されるSQLの中身までは制限できない

2つ目が見落としやすい点です。DROP TABLE のようなSQLも、同じコマンドで実行できてしまいます。start-query-execution には --work-group を指定できるので、集計専用のワークグループを作り、その実行に使うIAMロールをSELECTとログ用S3の読み取りだけに絞る構成が考えられます。拒否したい操作は、Claude Code側の permissions.deny にも書けます。ただし、広い deny は同じ形の狭い allow も打ち消す点に注意が必要です。

非対話モードで確認が必要な操作に当たると、既定では拒否されて実行が止まります。止まった操作は、許可ルールに足すか、人が手動で実行するかを選びます。

結果を渡して要約させる

クエリ結果をCSVで取り出したあとは、パイプで渡して要約させる使い方もあります。

cat top5xx.csv | claude -p "5xxが多い上位URLを、原因の仮説付きで3行にまとめて"

標準入力で渡せるのは10MBまでです。結果が大きいときは、ファイルに保存してパスを指定します。小さなCSVの集計は、Files APIとcode executionでCSVを集計する比較記事のような方法でも足ります。ログが大きくて手元のCSVに収まらないなら、Athenaで絞ってから渡す形になります。

ログに含まれるクライアントIPやURLのクエリ文字列には、個人情報が混じる場合があります。要約に回すのは集計済みの結果にとどめ、生の行は渡しません。同じ考え方の実例は、OktaのSystem Log集計の記事にもあります。

5xxが起きた時間帯を絞る

URL別の上位が出たら、次は「いつ起きたか」です。ALBログの time 列は string 型で、ISO 8601風のタイムスタンプが文字列として入っています。時間単位で数えるだけなら、先頭13文字(2026-10-26T03 の形)で切る方法が手軽です。

SELECT
  substr(time, 1, 13) AS hour_utc,
  count(*) AS hits_5xx
FROM alb_access_logs
WHERE day = '2026/10/26'
  AND elb_status_code >= 500
GROUP BY substr(time, 1, 13)
ORDER BY hour_utc;

time の値は、末尾が Z のUTC表記かを実データで確かめてください。UTCなら、日本時間の障害報告と突き合わせるときは9時間ずらして読みます。Claudeに頼むときは「UTCのまま出し、日本時間への換算は表の注記で示す」ように指定すると、集計結果とレポートの時刻が食い違いません。day はS3のキーの日付で、日本時間の日付とは一致しないことがあります。日本時間の1日分を見たいときは、2つの day にまたがるクエリになります。

許可ルールを設定ファイルに固定する

コマンドラインに毎回 --allowedTools を並べる代わりに、プロジェクトの設定ファイルへ置く方法もあります。allowとdenyを並べて書けます。

{
  "permissions": {
    "allow": [
      "Bash(aws athena start-query-execution *)",
      "Bash(aws athena get-query-execution *)",
      "Bash(aws athena get-query-results *)"
    ],
    "deny": [
      "Bash(aws s3 rm *)",
      "Bash(aws athena delete-work-group *)"
    ]
  }
}

denyに書いた操作は、同じ形のallowがあっても拒否されます。集計用のプロジェクトでは、書き込み系のコマンドをdenyに入れておくと、依頼文の書き漏れがあっても被害が広がりません。

つまずきやすい点

パーティション射影には、仕様上の制約がいくつかあります。結果が0件になる、といった症状は、多くがこのどれかです。

  • 範囲外の日付は0件になる:projection.day.range の開始日より前の日付を指定してもエラーにはならず、0行が返ります。古いログを読むなら、開始日を前倒しします
  • 登録済みのパーティションは無視される:射影を有効にすると、Glueデータカタログに登録済みのパーティション情報は使われなくなります
  • 空のパーティションが半数を超えると遅くなる:該当ログのない日が射影範囲の過半を占めると、通常のパーティションより遅くなることがあると説明されています。ログの保存期間より遥か前を開始日にしないほうが無難です
  • Athena以外では射影が効かない:Redshift SpectrumやEMRなどから同じテーブルを読むときは、標準のパーティション情報が使われます
  • SHOW PARTITIONS では見えない:射影されたパーティションは一覧に出ません。日付が正しく展開されているかは、day を指定した SELECT で確かめます

もう1つ、Claudeが書いたクエリは、結果を一度検算します。件数が想定より極端に少ないときは、まず SELECT count(*) FROM alb_access_logs WHERE day = '...' で、その日のデータが読めているかを確認します。正規表現が実際のログ形式と合っていないと、列に値が入りません。

AWSの運用全般をClaude Codeで進めるなら、aws-coreプラグインで用意されているIaCやIAMのskillsも合わせて検討できます。

まとめ

ALBログの集計は、DDLをドキュメントの例から外さず、day 条件を必ず付ける、という2点を守れば、Claude Codeに任せやすい作業です。人が決めるのは、対象バケットと期間、実行に使うワークグループと権限の範囲です。5xxの原因は、elb_status_code と target_status_code のどちらで出ているかで、まず調べる場所が分かれます。

参照した一次ソース

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

  1. docs.aws.amazon.com/athena/latest/ug/application-load-balancer-logs.html
  2. docs.aws.amazon.com/athena/latest/ug/create-alb-access-logs-table-partition-pr…
  3. docs.aws.amazon.com/athena/latest/ug/partition-projection.html
  4. docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-acce…
  5. code.claude.com/docs/en/headless
  6. code.claude.com/docs/en/permissions
この記事を共有:XはてブLinkedIn