NO IMAGE

CloudWatch Logs Insights Metrics Filter Lambda ECS EKS 横断本番ガイド

NO IMAGE
目次

1. この記事について

fig01: CloudWatch Logs Insights × Metrics Filter 全体アーキテクチャ

【観測性本番運用シリーズ Vol1/3】ログ・トレース・SLO の 3 層で本番観測性基盤を段階構築

  • Vol1 (本記事): CloudWatch Logs Insights × Metrics Filter による Lambda/ECS/EKS 横断ログ可視化基盤 ← 今ここ
  • Vol2 (近日公開): X-Ray + ADOT による分散トレース完全活用編 — CloudWatch ServiceLens / サービスマップ / サンプリングレート設計 / ADOT Collector Terraform 実装 / Lambda Layer + ECS サイドカー + EKS DaemonSet 対応
  • Vol3 (近日公開): CloudWatch Synthetics + Application Signals による SLO 駆動監視編 — SLO 設定 / エラーバジェット管理 / Synthetics Canary Terraform 実装 / アラートポリシー自動化 / CloudWatch Alarm 階層設計
前提シリーズ: AWS 本番運用シリーズ 9 巻 — Lambda 3 部作 + EKS 3 部作 + 観測性 3 部作で完結

  • Lambda 本番運用 3 部作: Vol1 Container Image デプロイ / Vol2 SnapStart 応用 / Vol3 Powertools+Layers — 本記事 §5 で Powertools Logger 構造化ログの Logs Insights クエリ最適化として参照
  • EKS 本番運用 3 部作: Vol1 Cluster+Karpenter / Vol2 IRSA / Vol3 ALB+Argo CD — 本記事 §5 で EKS Fluent Bit + Container Insights 統合の前提として参照
  • 観測性 3 部作 (本シリーズ): Lambda 3 部作・EKS 3 部作で構築したワークロードに対し、ログ (Vol1) → トレース (Vol2) → SLO (Vol3) の順で観測性レイヤーを追加し、AWS 本番運用シリーズ 9 巻体制を完成させます。

1-1. 本記事のゴール

本記事では、CloudWatch Logs Insights のクエリ言語 8 コマンドを体系的に習得し、Metrics Filter で抽出した独自メトリクスを CloudWatch Alarm + SNS に連携する本番観測性基盤を Terraform + AWS CLI + コンソールの 3 点セットで実装します。Lambda / ECS / EKS の 3 サービスを横断的に扱い、ログ集約パターン・コスト最適化・ダッシュボード運用 Runbook まで本記事 1 本で完結します。

記事を完走した時点で以下の成果物が手元に揃います。

  • Logs Insights クエリセット:
    fields / filter / stats / sort / limit / parse / display / dedup の 8 コマンドと、
    正規表現 / 時系列 bin() / 複合クエリを組み合わせた本番頻出パターン集。
    Lambda エラー追跡・ECS リクエスト分析・EKS Pod ログ横断検索に即投入可能。
  • Metrics Filter + Alarm Terraform:
    aws_cloudwatch_log_metric_filter / aws_cloudwatch_metric_alarm / aws_sns_topic を一気通貫で定義した設定。
    JSON ログ対応の metric_transformation ブロック記述例と Space-delimited / JSON / 正規表現 3 形式の filter pattern を網羅。
    カスタム Namespace 命名規約 (MyApp/Lambda/ErrorRate 等) と閾値設計指針込み。
  • Log Group 階層設計 Terraform:
    Standard Tier (Metrics Filter 利用可能) / IA Tier (アーカイブ用途・Metrics Filter 不可) を log_group_class で管理。
    保存期間 (dev:7d / prod:30d / security:90d / archive:365d) を locals で環境別に定義し、
    無期限保持の Log Group を検出する棚卸しコマンド付き。
  • CloudWatch Dashboard JSON + 運用 Runbook テンプレート 5 種:
    Logs Insights ウィジェット埋め込みダッシュボードと、
    障害切り分け / リクエスト追跡 / コスト監視 / SLO 違反対応 / アラーム棚卸の 5 テンプレートを含む運用ドキュメントセット。
    各テンプレートは「起動トリガ / Logs Insights クエリ / Metrics Filter / Dashboard 連携」の 4 軸で構造化。

1-2. 読者像

本記事は次のような技術背景を持つエンジニアを対象としています。共通の前提として Lambda / ECS / EKS のいずれかが本番稼働しており、CloudWatch Logs にアプリケーションログが流れている状態を想定しています。

Lambda 運用者向け

  • /aws/lambda/ 配下に関数ログが流れているが、コールドスタートや実行時間の傾向分析を Logs Insights で行う方法がわからない。@initDuration@billedDuration フィールドをどう集計するか不明。
  • Lambda Powertools Logger で構造化 JSON ログを出力しているが、parse コマンドで JSON フィールドを抽出して集計する複合クエリを自力で書けていない。
  • Lambda エラーレートを Metrics Filter で定量化して CloudWatch Alarm を設定したいが、JSON ログ用の filter pattern 構文が不明で着手できていない。

ECS 運用者向け

  • ECS Fargate で awslogs ドライバを使っているが、Log Group に保存期間を設定しておらず無期限保持状態のままストレージコストが増大している。
  • アクセスログから 5xx エラーレートを Metrics Filter で抽出して Alarm を設定したいが、Space-delimited と JSON 形式の使い分けに迷っており設定が進まない。
  • 複数タスクのログを横断検索したい場面で logGroupNames 配列と logGroupNamePatterns のどちらを使うべきかわからない。

EKS 運用者向け

  • EKS Cluster に Fluent Bit + Container Insights を設定済みではあるものの、全 Pod のログを横断集計する際の kubernetes.pod_name フィールドの扱い方がわからず、分析を進められない。
  • IRSA で Fluent Bit に CloudWatch Logs 書込権限を付与したが、Log Group が Pod 単位で断片化しており、横断分析のための命名規則が定まっていない。
  • EKS のデバッグ用 Log Group の IA Tier 移行を検討しているが、Container Insights 統合との整合性 (IA Tier で Container Insights メトリクス抽出が使えるか) が不明。

前提知識: AWS コンソール操作 (IAM / CloudWatch Logs) + Terraform 基本構文 (resource / locals / output) + AWS CLI v2 設定済みの実行環境。kubectl は §5 の EKS 部分のみ必要です。

1-3. なぜ今これを書くか

Tavily による競合調査 (2026-04 時点 / 10 件分析) の結果、「Lambda/ECS/EKS 3 サービスを横断し、Logs Insights × Metrics Filter × コスト最適化 × Dashboard 運用 Runbook を 1 本で完結させる日本語実装ガイド」は 0 件でした。既存の日本語記事は Logs Insights コンソール操作の入門記事か、単一サービスを対象とする Metrics Filter 設定記事に留まっています。

特に以下の 3 点が競合 10 件中 0 件の差別化領域として確認されました。

  1. Log Group IA Tier × Sampling × 保存期間設計のコスト最適化を Terraform 完全実装で提示:
    言及のみ (実装手順なし) の記事が 1 件あったのみ。IA Tier の Standard 比約 50% 削減効果を
    Terraform 実装ハンズオンと合わせて解説した記事は存在しなかった。
  2. 運用 Runbook テンプレート 5 種 (障害切り分け / リクエスト追跡 / コスト監視 / SLO 違反対応 / アラーム棚卸):
    Logs Insights クエリや Dashboard 設計を Runbook 形式でドキュメント化した記事は 0 件。
    本番運用チームがすぐに採用できる形式での提示は存在しなかった。
  3. 3 サービス × 4 軸比較マトリクス (ログドライバ / 集約先 / Container Insights 統合 / コスト):
    Lambda / ECS / EKS を横断的に扱った記事は競合 10 件中 0 件。
  4. Terraform 完全 IaC 化による全リソース管理:
    CloudWatch コンソール操作のみで設定・変更するケースが多数の中、本記事では Logs Insights クエリ定義・
    Metrics Filter・CloudWatch Alarm・SNS Topic・Dashboard の全リソースを Terraform
    aws_cloudwatch_* リソースで一気通貫に IaC 化します。変更履歴・コードレビュー・ロールバックを
    Git ベースで管理できる構成により、複数環境 (dev / staging / production) への横展開と
    チーム運用標準化を実現します。Dashboard JSON も Terraform 管理で drift を防止します。

本記事は Lambda 本番運用 3 部作 (Vol1 / Vol2 / Vol3) および EKS 本番運用 3 部作 (Vol1 / Vol2 / Vol3) で構築したワークロードに対し、「観測性レイヤーを後付けで追加する」実装ガイドです。Vol1 (本記事) でログ層の可視化基盤を確立し、Vol2 (X-Ray+ADOT) でトレース層、Vol3 (Application Signals+Synthetics) でメトリクス/SLO 層を整備することで、AWS 本番運用シリーズ 9 巻体制が完成します。

1-4. 本記事の構成と読み方

タイトル主な内容QG
§1この記事についてシリーズ概要 / ゴール / 読者像
§2前提・環境・準備AWS CLI / Terraform / IAM 権限 / 環境確認
§3Logs Insights クエリ言語徹底解説8 コマンド + 正規表現 + 複合クエリQG-1 brc-red
§4Metrics Filter + アラーム連携filter pattern + JSON 抽出 + Alarm + SNSQG-2 brc-red
§5Lambda/ECS/EKS ログ集約パターン3 サービス別設定 + Container Insights 統合QG-3 brc-yellow
§6高速クエリ + コスト最適化IA Tier + Sampling + 保存期間 + クエリキャッシュQG-4 brc-yellow
§7ダッシュボード + 運用 RunbookDashboard widget + Runbook 5 種テンプレートQG-5 brc-yellow
§8まとめ + 落とし穴 10 選チートシート + 落とし穴 + Vol2/3 予告

推奨読み方: §3 → §4 の順で Logs Insights と Metrics Filter を習得後、§5 で担当サービス (Lambda/ECS/EKS) の節を優先して読んでください。§6 のコスト最適化は §4 の Metrics Filter 設定を完了してから着手してください (IA Tier では Metrics Filter が使用不可のため移行前に設定が必須です)。

1-5. 本記事で活用する主要 Terraform リソース早見表

Terraform リソース対応 AWS API主要設定項目使用章
aws_cloudwatch_log_groupCreateLogGroupretention_in_days / log_group_class§2, §5, §6
aws_cloudwatch_query_definitionPutQueryDefinitionquery_string / log_group_names§3, §7
aws_cloudwatch_log_metric_filterPutMetricFilterfilter_pattern / metric_transformation§4
aws_cloudwatch_metric_alarmPutMetricAlarmthreshold / treat_missing_data§4
aws_sns_topicCreateTopicname / tags§4
aws_sns_topic_subscriptionSubscribeprotocol / endpoint§4
aws_cloudwatch_dashboardPutDashboarddashboard_body (JSON)§7

各リソースの depends_on 設定や IAM 権限は §2 で一括説明します。Terraform Provider バージョンは hashicorp/aws ~> 5.40 を前提としています。

Note: aws_cloudwatch_log_metric_filter は対象 Log Group が作成済みである必要があります。
Terraform では aws_cloudwatch_log_group.main.namelog_group_name 引数に直接参照することで
暗黙の depends_on が自動解決されます。Lambda 関数の Log Group (/aws/lambda/<function-name>)
はランタイムが初回実行時に自動作成するため、Terraform 管理外の Log Group に Metrics Filter を
設定する場合は data "aws_cloudwatch_log_group" "lambda" { name = "/aws/lambda/..." }
参照してください。

前提シリーズ: Lambda Container Image デプロイ完全ガイドを読む


2. 前提・環境・準備

本章では、記事を通じて使用するツールのバージョン確認・IAM 権限設定・Terraform バックエンドを準備します。環境が整っていれば §3 以降に進んでください。

2-1. 前提環境

本記事の実施に必要なツールと環境を以下に示します。Lambda / ECS / EKS のいずれか 1 サービスが本番稼働済みであり、そのログが CloudWatch Logs の任意の Log Group に流れていることを前提とします。

必要なツール

ツール推奨バージョン確認コマンド
AWS CLIv2.15 以上aws --version
Terraformv1.7 以上terraform version
Python3.10 以上 (バッチスクリプト用)python3 --version
kubectlv1.29 以上 (§5 EKS 部分のみ)kubectl version --client
jq1.6 以上 (CLI 結果整形)jq --version

環境確認コマンド

# AWS CLI 認証確認
aws sts get-caller-identity --query '{Account:Account,Arn:Arn}' --output table

# デフォルトリージョン確認
aws configure get region

# 対象 Log Group の確認 (例: Lambda 関数のログ)
aws logs describe-log-groups \
  --log-group-name-prefix "/aws/lambda" \
  --query 'logGroups[*].{name:logGroupName,storedBytes:storedBytes,retention:retentionInDays}' \
  --output table

環境変数設定例

プロファイルとリージョンを環境変数で固定しておくと、AWS CLI の各コマンドに --region / --profile を都度指定する手間が省けます。

export AWS_REGION="ap-northeast-1"
export AWS_PROFILE="your-profile-name"

2-2. IAM 権限

本記事の操作には以下の IAM 権限が必要です。Terraform を使用する場合は実行 IAM ロール / IAM ユーザーに以下のポリシーをアタッチしてください。

最小権限ポリシー (Terraform/CLI 共通)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "LogsInsightsAndMetricsFilter",
"Effect": "Allow",
"Action": [
  "logs:StartQuery",
  "logs:GetQueryResults",
  "logs:StopQuery",
  "logs:DescribeQueries",
  "logs:PutQueryDefinition",
  "logs:DeleteQueryDefinition",
  "logs:DescribeQueryDefinitions",
  "logs:PutMetricFilter",
  "logs:DeleteMetricFilter",
  "logs:DescribeMetricFilters",
  "logs:CreateLogGroup",
  "logs:PutRetentionPolicy",
  "logs:DescribeLogGroups",
  "logs:DescribeLogStreams",
  "logs:GetLogEvents",
  "logs:FilterLogEvents"
],
"Resource": "*"
 },
 {
"Sid": "CloudWatchAlarmAndDashboard",
"Effect": "Allow",
"Action": [
  "cloudwatch:PutMetricAlarm",
  "cloudwatch:DeleteAlarms",
  "cloudwatch:DescribeAlarms",
  "cloudwatch:PutDashboard",
  "cloudwatch:GetDashboard",
  "cloudwatch:DeleteDashboards",
  "cloudwatch:ListDashboards"
],
"Resource": "*"
 },
 {
"Sid": "SNSForAlarm",
"Effect": "Allow",
"Action": [
  "sns:CreateTopic",
  "sns:DeleteTopic",
  "sns:Subscribe",
  "sns:Unsubscribe",
  "sns:ListSubscriptionsByTopic",
  "sns:GetTopicAttributes",
  "sns:SetTopicAttributes"
],
"Resource": "*"
 }
  ]
}

2-3. ゴール状態の定義

記事を完走した時点で以下の状態が確認できます。

# Metrics Filter が設定されていることを確認
aws logs describe-metric-filters \
  --log-group-name "/aws/lambda/prod-my-app" \
  --query 'metricFilters[*].{name:filterName,pattern:filterPattern}' \
  --output table

# CloudWatch Alarm が作成されていることを確認
aws cloudwatch describe-alarms \
  --alarm-name-prefix "prod-my-app" \
  --query 'MetricAlarms[*].{name:AlarmName,state:StateValue}' \
  --output table

# Dashboard が存在することを確認
aws cloudwatch list-dashboards \
  --dashboard-name-prefix "production-observability" \
  --query 'DashboardEntries[*].{name:DashboardName,size:Size}' \
  --output table

# Log Group の階層設計が適用されていることを確認
aws logs describe-log-groups \
  --query 'logGroups[*].{name:logGroupName,class:logGroupClass,retention:retentionInDays}' \
  --output table

全コマンドが正常に実行でき、各リソースが一覧に表示される状態が完走の定義です。


3. CloudWatch Logs Insights 基礎・クエリ言語徹底解説

CloudWatch Logs Insights は、CloudWatch Logs に蓄積されたログデータに対してアドホックな全文検索・集計・可視化を実行できるマネージドクエリエンジンです。専用のクエリ言語(Logs Insights QL)は fields / filter / stats / sort / parse / display / limit / dedup の 8 コマンドで構成されており、「直近 1 時間の Lambda エラー件数を 5 分粒度で集計する」「ECS の遅延リクエストを P99 で時系列グラフ化する」といった本番運用で頻出の分析を数行のクエリで記述できます。本章では 8 コマンドの詳細解説から Lambda/ECS/EKS 横断の複合クエリ実例、Terraform による保存管理まで一気通貫で説明します。

fig02: クエリ実行フロー

sequenceDiagram
 participant U as ユーザー
 participant API as Logs Insights API
 participant IDX as インデックスエンジン
 participant LG as Log Group
 participant RS as 結果セット

 U->>API: StartQuery (queryString + 時間範囲 + logGroupNames)
 API->>IDX: インデックス検索 (対象 Log Group を特定)
 IDX->>LG: ログストリームを並列スキャン開始
 LG-->>IDX: 生ログイベント (最大 50 並列)
 IDX->>IDX: filter / parse / stats を段階適用
 IDX->>IDX: サンプリング + 集計 + ソート
 IDX-->>API: 中間結果セット (最大 10,000 件)
 API-->>U: queryId 返却 (非同期)
 U->>API: GetQueryResults (queryId)
 API-->>RS: 結果キャッシュ照合 (7 日間保持)
 RS-->>U: 完了 / 結果セット + 統計情報

3-1. fields / filter — 基本構文とフィルタリング

fields コマンドは返却するフィールドを選択します。ログイベントには CloudWatch Logs が自動付与する @timestamp / @message / @logStream / @log の 4 フィールドが常に利用可能です。Lambda 関数では追加で @requestId / @duration / @billedDuration / @memorySize / @maxMemoryUsed / @initDuration が利用できます。

fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 50

filter コマンドは SQL の WHERE 句に相当します。比較演算子 = / != / < / <= / > / >= と文字列マッチ演算子 like / not like が使えます。正規表現マッチには =~ を使用します。and / or / not による複合条件も記述可能です。

filter 代表パターン:

用途クエリ説明
エラーメッセージ絞り込みfilter @message like /ERROR/大文字小文字区別あり
JSON フィールド条件filter level = "ERROR"構造化ログのフィールドを直接参照
複合条件filter level = "ERROR" and service = "api"AND / OR / NOT 演算子使用可
正規表現マッチfilter @message =~ /TimeoutException/=~ で正規表現マッチ
値の存在確認filter ispresent(responseTime)NULL / 欠損値を除外

3-2. stats — 集計と時系列 bin()

stats コマンドは SQL の GROUP BY + 集約関数に相当します。count() / sum() / avg() / min() / max() / pct() の 6 種類の集約関数が使えます。by 句でグルーピングフィールドを指定し、bin(時間幅) と組み合わせることで時系列グラフを生成できます。

fields @timestamp, statusCode, responseTime
| filter ispresent(responseTime)
| stats avg(responseTime) as avg_ms,
  pct(responseTime, 99) as p99_ms,
  count() as req_count
 by bin(5m)

stats 代表パターン:

集計内容クエリ用途
エラー件数stats count() as errors by bin(1h)1 時間単位エラー件数
平均レイテンシstats avg(duration) by functionName関数別平均実行時間
P99 レイテンシstats pct(responseTime, 99) as p99 by bin(5m)5 分粒度 P99 監視
ユニーク数stats count_distinct(userId) as usersDAU/MAU 計測
サービス別エラーstats count() as cnt by service, statusCodeサービス × ステータス別集計

bin() 粒度の選び方:

粒度用途注意
bin(1m)リアルタイム障害調査クエリスキャン量が増加するため短期間に限定
bin(5m)ダッシュボード標準・アラーム連動バランスが良くコスト効率的
bin(1h)日次傾向分析・コスト集計長期間クエリでも応答が速い
bin(1d)週次レポート・長期トレンドCloudWatch Dashboard の月次グラフ向け

3-3. parse — 正規表現と JSON フィールド抽出

parse コマンドは非構造化ログや混在ログから特定フィールドを抽出します。正規表現抽出glob パターン抽出 の 2 形式があります。

正規表現によるフィールド抽出:

parse @message /\[(?<level>[A-Z]+)\] (?<requestId>[a-f0-9-]+) (?<message>.+)/
| filter level = "ERROR"
| stats count() by level

名前付きキャプチャグループ (?<fieldName>...) を使用することで、抽出した値を後続コマンドで参照できます。

glob パターンによるフィールド抽出:

parse @message "* [*] * * *" as timestamp, level, requestId, module, msg
| filter level = "ERROR"
| fields @timestamp, level, requestId, msg

JSON ログのフィールド直接参照:

Lambda Powertools Logger / ECS / EKS が出力する JSON ログは、Logs Insights がトップレベルフィールドを自動展開するため parse なしで直接参照できます。ネストされたフィールドは glob パターンで抽出します。

fields @timestamp, level, service, message, statusCode
| filter level = "ERROR"
| stats count() as errors by service, statusCode
| sort errors desc
| limit 20
parse @message '"traceId":"*"' as traceId
| filter ispresent(traceId)
| fields @timestamp, traceId, message
| limit 50

3-4. sort / display / limit / dedup

sort は指定フィールドで昇順 (asc) または降順 (desc) に並び替えます。display は最終出力フィールドを絞り込みます(fields との違いは全パイプライン処理後に適用される点です)。limit は返却件数を制限します(最大 10,000 件)。dedup は指定フィールドでの重複排除を行います。

fields @timestamp, @logStream, level, message, requestId
| filter level in ["ERROR", "WARN"]
| dedup requestId
| sort @timestamp desc
| display @timestamp, level, message, requestId
| limit 100

sort / display / limit / dedup 活用パターン:

コマンド用途例注意点
sort responseTime desc遅延リクエストの上位抽出大規模ログは sort 前に filter で絞り込む
display @timestamp, message出力フィールドを最小化して可読性向上display は最後のコマンドとして配置
limit 1000長期間クエリのタイムアウト防止デフォルト 1,000 件・最大 10,000 件
dedup @requestId同一リクエストの重複エラーを排除ログストリーム横断の重複削除に有効

3-5. 複合クエリ実例 — Lambda / ECS / EKS

本番環境で頻出する 3 サービスの代表クエリパターンを示します。

Lambda エラー率 (5 分粒度):

fields @timestamp, @requestId, statusCode, errorMessage
| filter level = "ERROR" or statusCode >= 500
| stats count() as errors by bin(5m)
| sort @timestamp desc

ECS レスポンスタイム P50 / P95 / P99 + サービス別分析:

fields @timestamp, service, responseTime, statusCode
| filter ispresent(responseTime) and service = "ecs-api"
| stats pct(responseTime, 50) as p50_ms,
  pct(responseTime, 95) as p95_ms,
  pct(responseTime, 99) as p99_ms,
  count() as request_count
 by bin(5m)
| sort @timestamp desc

EKS Fluent Bit ログ — Pod 別エラー集計:

fields @timestamp, kubernetes.pod_name, kubernetes.namespace_name, log, level
| filter kubernetes.namespace_name = "production"
| filter level = "ERROR" or log like /Exception/
| stats count() as errors by kubernetes.pod_name
| sort errors desc
| limit 30

Lambda Powertools Logger を使用している場合、service / level / cold_start / xray_trace_id フィールドが JSON ログに自動付与されます。filter cold_start = 1 でコールドスタートのみ絞り込む、stats count() as cold_starts, avg(duration) as avg_duration by functionName で関数別コールドスタート状況を可視化するなど、Lambda 応用シリーズの既存実装とそのまま組み合わせられます。

3-6. Terraform: aws_cloudwatch_query_definition

よく使うクエリを aws_cloudwatch_query_definition リソースで管理することで、チーム全員が同一クエリを CloudWatch コンソールから呼び出せます。クエリのバージョン管理と再利用性が向上します。

# Lambda エラー率クエリ定義
resource "aws_cloudwatch_query_definition" "lambda_error_rate" {
  name = "${var.project_name}/lambda-error-rate"

  log_group_names = [
 aws_cloudwatch_log_group.lambda.name,
  ]

  query_string = <<-QUERY
 fields @timestamp, @requestId, statusCode, errorMessage
 | filter level = "ERROR" or statusCode >= 500
 | stats count() as errors by bin(5m)
 | sort @timestamp desc
  QUERY
}

# ECS レスポンスタイム P99 クエリ定義
resource "aws_cloudwatch_query_definition" "ecs_latency_p99" {
  name = "${var.project_name}/ecs-latency-p99"

  log_group_names = [
 aws_cloudwatch_log_group.ecs_app.name,
  ]

  query_string = <<-QUERY
 fields @timestamp, service, responseTime, statusCode
 | filter ispresent(responseTime)
 | stats pct(responseTime, 50) as p50_ms,
pct(responseTime, 95) as p95_ms,
pct(responseTime, 99) as p99_ms,
count() as request_count
  by bin(5m)
 | sort @timestamp desc
  QUERY
}

# EKS Pod 別エラー集計クエリ定義
resource "aws_cloudwatch_query_definition" "eks_pod_errors" {
  name = "${var.project_name}/eks-pod-errors"

  log_group_names = [
 aws_cloudwatch_log_group.eks_app.name,
  ]

  query_string = <<-QUERY
 fields @timestamp, kubernetes.pod_name, kubernetes.namespace_name, log, level
 | filter kubernetes.namespace_name = "production"
 | filter level = "ERROR" or log like /Exception/
 | stats count() as errors by kubernetes.pod_name
 | sort errors desc
 | limit 30
  QUERY
}

AWS CLI によるクエリ定義確認と実行:

# 保存済みクエリ定義の一覧
aws logs describe-query-definitions \
  --query 'queryDefinitions[*].{name:name,id:queryDefinitionId}' \
  --output table

# クエリの即時実行 (過去 1 時間)
aws logs start-query \
  --log-group-name "/aws/lambda/prod-my-app" \
  --start-time $(date -d "1 hour ago" +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp, @message | filter @message like /ERROR/ | limit 50'

# クエリ結果の取得
QUERY_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
aws logs get-query-results --query-id "$QUERY_ID" \
  --query 'results[*][*].{field:field,value:value}' \
  --output json

コンソールでの操作手順:

  1. CloudWatch → ログのインサイト を開きます。
  2. ロググループ選択フィールドで対象 Log Group を 1 つ以上選択します(複数 Log Group の横断クエリも可能です)。
  3. クエリエディタに Logs Insights QL を入力します。
  4. 時間範囲を選択し、クエリの実行 をクリックします。
  5. stats ... by bin(N) 形式のクエリは 折れ線グラフ タブで時系列グラフとして表示できます。
  6. 再利用するクエリは クエリを保存 から名前をつけて保存し、クエリライブラリ から呼び出せます。

QG-1: Logs Insights クエリ言語チートシート — 8 コマンド × 代表ユースケース 3 件マトリクス

コマンドユースケース 1: エラー分析ユースケース 2: レイテンシ集計ユースケース 3: リソース追跡
fieldsfields @timestamp, level, message — エラーメッセージと時刻を返却fields @timestamp, responseTime, service — レイテンシフィールドを選択fields @timestamp, @requestId, @memorySize, @maxMemoryUsed — Lambda リソース使用量確認
filterfilter level = "ERROR" — ERROR ログのみ絞り込みfilter ispresent(responseTime) and service = "api" — 対象サービス + 値存在確認filter @maxMemoryUsed / @memorySize > 0.9 — メモリ使用率 90% 超のみ
statsstats count() as errors by bin(5m) — 5 分粒度エラー件数stats pct(responseTime, 99) as p99 by bin(5m) — 5 分粒度 P99 レイテンシstats avg(@maxMemoryUsed) by functionName — 関数別平均メモリ使用量
sortsort errors desc — エラー件数の多い順sort p99 desc — P99 の高い時間帯を上位表示sort avg_memory desc — メモリ消費の多い関数を上位へ
parseparse @message /\[ERROR\] (?<reqId>\S+)/ — requestId 抽出parse @message '"responseTime":*,' as rt — glob でレイテンシ抽出parse @message '"traceId":"*"' as traceId — X-Ray traceId 抽出
displaydisplay @timestamp, level, message, requestId — 必要フィールドのみ表示display @timestamp, p50_ms, p95_ms, p99_ms — パーセンタイル列のみ表示display functionName, avg_memory, req_count — サマリービューを最小化
limitlimit 100 — エラー上位 100 件取得limit 1000 — 長期間クエリの件数上限設定limit 30 — Pod 上位 30 件でダッシュボード表示
dedupdedup @requestId — 同一リクエストの重複エラーを排除dedup sessionId — セッション単位で重複排除dedup kubernetes.pod_name — Pod 単位でユニーク表示

■ よく使う bin() 粒度

  • bin(1m): リアルタイム障害調査(クエリスキャン量に注意)
  • bin(5m): 本番アラーム連動・ダッシュボード標準
  • bin(1h): 日次傾向分析・コスト集計
  • bin(1d): 週次レポート・長期トレンド

■ Logs Insights 制限事項 (2026-04 時点)

  • 同時実行クエリ上限: 10 / アカウント / リージョン(デフォルト・Service Quotas からクォータ増加申請可)
  • クエリ結果の最大件数: 10,000 件(limit コマンドで制御)
  • クエリ実行時間の上限: 60 分(超過すると自動キャンセル)
  • 結果の保持期間: 7 日間(GetQueryResults で queryId を再利用してスキャンコストをゼロにできる)
  • 1 クエリあたり指定可能な Log Group 数: 50 グループ

4. Metrics Filter によるメトリクス抽出とアラーム連携

fig03: Metrics Filter → Custom Metric → Alarm → SNS

CloudWatch Logs の Metrics Filter は、ログストリームをリアルタイムにスキャンし、特定のパターンにマッチした件数や数値を CloudWatch カスタムメトリクスとして発行する機能です。Logs Insights がアドホック分析向けであるのに対し、Metrics Filter は定常監視・アラーム連動に特化しており、「ログに ERROR が何件あったか」を 1 分ごとに計測し続けることができます。本章では filter pattern 3 種の書き方から Terraform 完全実装、Anomaly Detection を含む複合アラームの設計まで解説します。

4-1. Metrics Filter の仕組み

Metrics Filter は Log Group に対してルールとして設定されます。ログイベントが書き込まれるたびにすべての Metrics Filter がログを評価し、マッチした場合は指定した namespace / metric_name でカスタムメトリクスを発行します。発行されたメトリクスを CloudWatch Alarm が定期評価し、閾値超過時に SNS Topic → Email / Lambda / PagerDuty へファンアウトします。

1 つの Log Group に最大 100 件の Metrics Filter を設定できます。1 つの Metrics Filter には 1 つの metric_transformation のみ設定できるため、複数メトリクスが必要な場合は Metrics Filter を複数作成します。

4-2. filter pattern 3 種の書き方

CloudWatch Logs Metrics Filter は Space-delimited / JSON / 正規表現 の 3 種類の filter pattern をサポートします。

Space-delimited pattern

スペース区切りのログ(Apache / nginx アクセスログなど)に使用します。フィールドを角括弧で列挙し、条件を = や数値比較で指定します。

[host, ident, authuser, timestamp, request, status_code=5*, bytes]

5* はワイルドカードで 5xx エラーをすべてマッチします。不要フィールドは ... で省略できます。4xx 検出なら status_code=4*、POST かつ大容量なら [..., method=POST, ..., bytes>10000] のように条件を組み合わせられます。

JSON pattern

JSON 形式のログ(Lambda Powertools Logger / ECS / EKS 構造化ログ)に使用します。Lambda / ECS / EKS の多くはデフォルトで JSON 形式のログを出力するため、本番環境で最もよく使う記法です。

{ $.statusCode = 500 }

複合条件は &&|| で組み合わせられます。数値フィールドには比較演算子 = / != / < / <= / > / >= が使用可能です。

{ $.statusCode = 500 && $.service = "api-gateway" }
{ ($.level = "ERROR") || ($.level = "FATAL") }
{ $.responseTime > 3000 }
{ $.message = "*TimeoutException*" }

ネストされた JSON フィールドも $.parent.child 形式で指定できます。

{ $.error.code = "ECONNREFUSED" }
{ $.request.path = "/api/v1/*" }

正規表現 pattern

% で囲んで記述します。非構造化ログや syslog など、JSON 以外のログに使用します。

%Exception%
%ERROR.*database%

4-3. JSON ログからのメトリクス抽出パターン

Lambda Powertools Logger や ECS / EKS の構造化ログでは JSON 形式が標準です。代表的な抽出パターンを示します。

HTTP 500 エラー件数カウント:

{ $.statusCode = 500 }

metric_value に 1 を設定すると、マッチ 1 件につきカウント +1 を発行します。1 分間の Sum を CloudWatch Alarm で監視します。

レスポンスタイムを発行する場合は { $.responseTime > 0 } + value = "$.responseTime" を指定します。これによりログ内の数値フィールドをそのままメトリクス値として発行でき、P99 レイテンシのアラームが実現可能です。同一 Log Group に複数サービスのログが混在する場合、{ $.service = "lambda-handler" && $.level = "ERROR" } のように service ラベルを組み合わせ、Metrics Filter を複数作成してサービス別に分離します。

4-4. Custom Metric namespace 設計

Custom Metric の namespace は CloudWatch コンソールの「すべてのメトリクス」における分類キーです。命名規約を決めていないとメトリクスが散在し、ダッシュボード管理が困難になります。

推奨 namespace 体系:

サービスnamespacemetric_name 例
LambdaCustomMetrics/LambdaErrorCount, SlowInvocationCount, TimeoutCount
ECSCustomMetrics/ECSTaskErrorCount, Http5xxCount, ResponseTimeP99
EKSCustomMetrics/EKSPodErrorCount, ApiServerErrorCount, DBConnectionError
共通CustomMetrics/ApplicationCriticalAlertCount, AuthFailureCount

命名 3 原則:

  1. サービス別に namespace を分けるCustomMetrics/LambdaCustomMetrics/ECS を混在させない
  2. metric_name は動作 + 計測対象形式ErrorCount / LatencyMs / ThrottleCount など動詞 + 名詞形式で統一
  3. Dimension で環境分離Environment: production / FunctionName: my-lambda を Dimension に追加して namespace を環境間で共有可能にする

4-5. Terraform 完全実装

Metrics Filter → Custom Metric → Alarm → SNS の全リソースを Terraform で構築します。

main.tf(Metrics Filter + Alarm + SNS セット):

# SNS Topic (アラーム通知先)
resource "aws_sns_topic" "alert" {
  name = "${var.project_name}-alert"
}

resource "aws_sns_topic_subscription" "email" {
  topic_arn = aws_sns_topic.alert.arn
  protocol  = "email"
  endpoint  = var.alert_email
}

# Metrics Filter: 500 エラーカウント
resource "aws_cloudwatch_log_metric_filter" "lambda_error" {
  name  = "${var.project_name}-lambda-error"
  log_group_name = aws_cloudwatch_log_group.lambda.name
  pattern  = "{ $.statusCode = 500 }"

  metric_transformation {
 name = "ErrorCount"
 namespace  = "CustomMetrics/Lambda"
 value= "1"
 default_value = "0"
 unit = "Count"
 dimensions = {
FunctionName = var.function_name
Environment  = var.environment
 }
  }
}

# CloudWatch Alarm: エラーレート監視
resource "aws_cloudwatch_metric_alarm" "lambda_error_rate" {
  alarm_name = "${var.project_name}-lambda-error-rate"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 2
  metric_name= "ErrorCount"
  namespace  = "CustomMetrics/Lambda"
  period  = 60
  statistic  = "Sum"
  threshold  = 5
  alarm_description= "Lambda エラーが 1 分間に 5 件以上発生"
  treat_missing_data  = "notBreaching"

  dimensions = {
 FunctionName = var.function_name
 Environment  = var.environment
  }

  alarm_actions = [aws_sns_topic.alert.arn]
  ok_actions = [aws_sns_topic.alert.arn]
}

レスポンスタイムを発行する場合は pattern = "{ $.responseTime > 0 }" / value = "$.responseTime" / unit = "Milliseconds" に変更し、Alarm で extended_statistic = "p99"threshold = 3000 を設定することで P99 レイテンシアラームを追加できます。

treat_missing_data = "notBreaching" を設定することで、低トラフィック時間帯にアラームが誤発砲することを防ぎます。デフォルト値(missing)のままにすると、ログが流入しない深夜帯でアラームが ALARM 状態になることがあります。

4-6. AWS CLI による設定

# Metrics Filter 作成
aws logs put-metric-filter \
  --log-group-name "/aws/lambda/my-function" \
  --filter-name "lambda-500-error" \
  --filter-pattern '{ $.statusCode = 500 }' \
  --metric-transformations \
 metricName=ErrorCount,metricNamespace=CustomMetrics/Lambda,metricValue=1,defaultValue=0,unit=Count

# 既存 Metrics Filter 一覧確認
aws logs describe-metric-filters \
  --log-group-name "/aws/lambda/my-function" \
  --query 'metricFilters[*].{Name:filterName,Pattern:filterPattern,Metric:metricTransformations[0].metricName}'

# CloudWatch Alarm 作成
aws cloudwatch put-metric-alarm \
  --alarm-name "lambda-error-rate" \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --evaluation-periods 2 \
  --metric-name ErrorCount \
  --namespace CustomMetrics/Lambda \
  --period 60 \
  --statistic Sum \
  --threshold 5 \
  --treat-missing-data notBreaching \
  --alarm-actions "arn:aws:sns:ap-northeast-1:123456789012:my-alert"

SNS サブスクリプション登録後は確認メールの承認を忘れずに行います。

4-7. コンソールでの設定手順

  1. CloudWatch → ロググループ を開き対象 Log Group を選択する
  2. アクション → メトリクスフィルターの作成 をクリックする
  3. filter pattern を入力し、パターンのテスト でサンプルログを貼り付けてマッチ動作を確認する
  4. 次のステップ → 名前空間 CustomMetrics/Lambda、メトリクス名 ErrorCount、単位 Count を入力する
  5. フィルターの作成 で完了する

パターンのテスト機能を使うと、実際のログ文字列を貼り付けてマッチするかを事前確認できます。Alarm は CloudWatch → アラーム → アラームの作成 → メトリクスを選択CustomMetrics/LambdaErrorCount の手順で設定します。

4-8. Anomaly Detection と Composite Alarm

Anomaly Detection アラーム

トラフィックが時間帯により大きく変動する場合、固定閾値では昼間は適切でも深夜帯に誤発砲するケースがあります。Anomaly Detection は CloudWatch が過去の統計から「正常範囲」を自動計算し、その範囲を逸脱した場合にアラームを発火します。

resource "aws_cloudwatch_metric_alarm" "lambda_anomaly" {
  alarm_name = "${var.project_name}-lambda-error-anomaly"
  comparison_operator = "GreaterThanUpperThreshold"
  evaluation_periods  = 2
  threshold_metric_id = "ad1"
  alarm_description= "Lambda エラーが異常検知モデルの上限を超過"
  treat_missing_data  = "notBreaching"
  alarm_actions = [aws_sns_topic.alert.arn]

  metric_query {
 id = "m1"
 return_data = false
 metric {
metric_name = "ErrorCount"
namespace= "CustomMetrics/Lambda"
period= 300
stat  = "Sum"
dimensions  = { FunctionName = var.function_name, Environment = var.environment }
 }
  }

  metric_query {
 id = "ad1"
 expression  = "ANOMALY_DETECTION_BAND(m1, 2)"
 label = "ErrorCount (Anomaly Band)"
 return_data = true
  }
}

ANOMALY_DETECTION_BAND(m1, 2) の第 2 引数は標準偏差の倍率です。2 で約 95.4%、3 で約 99.7% の正常範囲をカバーします。誤検知を避けるには 2 から始めて段階的に調整することを推奨します。

Composite Alarm

Composite Alarm は複数のアラームを AND / OR 条件で結合します。aws_cloudwatch_composite_alarm リソースの alarm_rule"ALARM(alarm-name-A) AND ALARM(alarm-name-B)" 形式で記述します。子アラームには alarm_actions を設定せず、Composite Alarm にのみ設定することで SNS への重複通知を排除し、アラームストームを防げます。


QG-2: Metrics Filter pattern 構文 + JSON ログ抽出 + Alarm 連携チェックリスト

■ filter pattern 3 種

  • Space-delimited: [host, ..., status_code=5*, bytes] — フィールド位置指定、アクセスログ / syslog 向け
  • JSON: { $.statusCode = 500 } — ネスト JSON 対応、Lambda / ECS / EKS 構造化ログで最頻用途。&& / || / 比較演算子使用可
  • 正規表現: %ERROR.*timeout% — 非構造化ログ / 混在ログ向け、% で囲む

■ namespace 命名規約

  • CustomMetrics/Lambda / CustomMetrics/ECS / CustomMetrics/EKS でサービス分離
  • metric_name は動作 + 計測対象形式(ErrorCount / ResponseTimeMs / ThrottleCount
  • Dimension に EnvironmentFunctionName(または ServiceName)を追加して環境分離

■ Alarm 閾値設計 5 項目

  • comparison_operator: エラー件数には GreaterThanOrEqualToThreshold、レイテンシには GreaterThanThreshold を使い分ける
  • evaluation_periods: 2〜3 period 連続超過を推奨(瞬間スパイクによる誤発砲防止)
  • treat_missing_data: 必ず notBreaching に設定(デフォルト missing は低トラフィック時間帯に誤発砲する)
  • Anomaly Detection: 変動が大きいメトリクスには ANOMALY_DETECTION_BAND(m1, 2) で動的閾値化(標準偏差 2 = 約 95.4% の正常範囲)
  • Composite Alarm: 複数条件を AND/OR 結合してアラームストーム防止 + 子アラームに alarm_actions 不要で SNS 重複通知を排除

5. Lambda/ECS/EKS 各サービスからのログ集約パターン

Lambda・ECS Fargate・EKS という 3 つのコンピュートサービスは、それぞれ異なるログ収集メカニズムを持ちます。本章では、各サービスのログドライバ・集約先・Container Insights 統合可否・コスト特性の 4 軸で横断比較し、Terraform / AWS CLI / コンソールの 3 形式でログ集約基盤を実装します。

fig04: ログ集約フロー比較

flowchart TD
  subgraph Lambda["Lambda 関数"]
 L1["Lambda Handler\n(stdout/stderr)"] --> L2["CloudWatch Logs\n自動連携"]
  end
  subgraph ECS["ECS Fargate"]
 E1["コンテナ stdout/stderr"] --> E2["awslogs ドライバ\nlogConfiguration"]
  end
  subgraph EKS["EKS ノード"]
 K1["Pod stdout/stderr"] --> K2["Fluent Bit DaemonSet\namazon-cloudwatch-observability"]
  end

  L2 --> CWL["CloudWatch Logs"]
  E2 --> CWL
  K2 --> CWL
  CWL --> LI["Logs Insights\nクエリ分析"]
  CWL --> CI["Container Insights\nメトリクス集約"]
QG-3: Lambda/ECS/EKS サービス別ログ集約パターンマトリクス

サービスログドライバ / 収集方式集約先Container Insights 統合コスト目安
LambdaCloudWatch Logs ネイティブ統合
(実行ロールで自動収集)
/aws/lambda/<function-name>Lambda Insights アドオン(layers 追加)で統合可能インジェスト: $0.50/GB
Insights クエリ: $0.005/GB スキャン
ECS Fargateawslogs ドライバ
logConfiguration / awslogs-group
/ecs/<service-name>(任意設定)CloudWatch Agent サイドカー経由でクラスター単位に有効化インジェスト: $0.50/GB
awslogs ドライバ自体は無料
EKSFluent Bit DaemonSet
amazon-cloudwatch-observability アドオン)
/aws/containerinsights/<cluster-name>/applicationアドオン有効化で Container Insights を自動統合
(IRSA による IAM 権限委譲必須)
インジェスト: $0.50/GB
Container Insights メトリクス: $0.035/1000 件

5-1. Lambda: CloudWatch Logs ネイティブ統合

Lambda 関数は IAM 実行ロールに logs:CreateLogGroup / logs:CreateLogStream / logs:PutLogEvents 権限があれば、CloudWatch Logs への書き込みを自動で行います。標準出力に出力したテキストはすべて /aws/lambda/<function-name> の Log Group に収集されます。Lambda Powertools + Layers 活用ガイド Vol3 で解説している Powertools Logger を組み合わせることで、Logs Insights によるクエリ分析効率が大幅に向上します。

Terraform 実装

# Lambda 専用 Log Group (Terraform で事前作成)
resource "aws_cloudwatch_log_group" "lambda_app" {
  name  = "/aws/lambda/${aws_lambda_function.my_app.function_name}"
  retention_in_days = 30
  kms_key_id  = aws_kms_key.log_key.arn

  tags = {
 Environment = "production"
 Service  = "lambda"
  }
}

resource "aws_lambda_function" "my_app" {
  function_name = "my-app"
  handler = "app.lambda_handler"
  runtime = "python3.12"
  filename= "function.zip"
  role = aws_iam_role.lambda_exec.arn

  environment {
 variables = {
POWERTOOLS_SERVICE_NAME = "my-app"
LOG_LEVEL= "INFO"
 }
  }
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
  role = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

AWS CLI による Lambda Log Group 確認

# Lambda 関数の Log Group を一覧表示
aws logs describe-log-groups \
  --log-group-name-prefix "/aws/lambda/" \
  --query 'logGroups[*].{name:logGroupName,retention:retentionInDays}' \
  --output table

# 最新ログストリームを確認
aws logs describe-log-streams \
  --log-group-name "/aws/lambda/my-app" \
  --order-by LastEventTime \
  --descending \
  --max-items 5

Powertools Logger による構造化 JSON ログ実装例

from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger(service="my-app")

@logger.inject_lambda_context(correlation_id_path="headers.X-Correlation-Id")
def lambda_handler(event: dict, context: LambdaContext) -> dict:
 logger.info("処理開始", extra={"user_id": event.get("user_id")})
 try:
  result = process(event)
  logger.info("処理完了", extra={"result_count": len(result)})
  return {"statusCode": 200, "body": result}
 except Exception as e:
  logger.exception("処理エラー", extra={"error": str(e)})
  raise

inject_lambda_context デコレータにより cold_start / function_name / request_id が自動付与された構造化 JSON ログが書き込まれます。§3 の Logs Insights parse コマンドで JSON フィールドを直接抽出できます。

コンソール手順: CloudWatch → Logs → Log groups → /aws/lambda/my-app → 「Log streams」タブから最新ストリームを選択してログを確認します。


5-2. ECS: awslogs ドライバによるログ集約

ECS Fargate では、タスク定義の logConfigurationawslogs ドライバを指定することで、コンテナの stdout/stderr を CloudWatch Logs に直接転送します。追加のログエージェントが不要で設定もシンプルなため、ECS の標準ログ集約方式として広く採用されています。

Terraform 実装 (タスク定義)

# ECS 専用 Log Group
resource "aws_cloudwatch_log_group" "ecs_service" {
  name  = "/ecs/my-service"
  retention_in_days = 30
  kms_key_id  = aws_kms_key.log_key.arn

  tags = {
 Environment = "production"
 Service  = "ecs"
  }
}

# ECS タスク定義 (awslogs ドライバ)
resource "aws_ecs_task_definition" "my_service" {
  family = "my-service"
  requires_compatibilities = ["FARGATE"]
  network_mode = "awsvpc"
  cpu = "512"
  memory = "1024"
  execution_role_arn = aws_iam_role.ecs_task_execution.arn

  container_definitions = jsonencode([
 {
name  = "app"
image = "my-app:latest"
logConfiguration = {
  logDriver = "awslogs"
  options = {
 "awslogs-group"= "/ecs/my-service"
 "awslogs-region"  = "ap-northeast-1"
 "awslogs-stream-prefix" = "app"
 "awslogs-create-group"  = "false"
  }
}
 }
  ])
}

resource "aws_iam_role_policy_attachment" "ecs_task_logs" {
  role = aws_iam_role.ecs_task_execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

AWS CLI による ECS ログ確認

# ECS サービスの Log Group を確認
aws logs describe-log-groups \
  --log-group-name-prefix "/ecs/" \
  --query 'logGroups[*].{name:logGroupName,retention:retentionInDays}' \
  --output table

# ECS タスクの最新ログを取得
aws logs tail "/ecs/my-service" \
  --since 1h \
  --format short

コンソール手順 (awslogs ドライバ設定)

  1. ECS コンソール → 「タスク定義」→「新しいタスク定義の作成」をクリックします。
  2. コンテナ設定画面の「ログ設定」で「Amazon CloudWatch」を選択します。
  3. ロググループに /ecs/<service-name> を入力します。
  4. ストリームプレフィックスに app を入力して保存します。

クロスリンク: ecspresso によるタスク定義の差分管理の詳細は ecspresso 徹底活用ガイド (WP:1343) を参照してください。awslogs ドライバの awslogs-multiline-pattern オプションを使用すると、スタックトレースなどの複数行ログを 1 イベントに集約できます。


5-3. EKS: Fluent Bit + Container Insights 統合

EKS では Amazon CloudWatch Observability アドオン (amazon-cloudwatch-observability) を有効化することで、Fluent Bit DaemonSet が自動デプロイされ、全 Pod の stdout/stderr が CloudWatch Logs に転送されます。IRSA (IAM Roles for Service Accounts) による権限委譲が必須です。

Terraform 実装 (EKS アドオン + IRSA)

# Container Insights 用 Log Group
resource "aws_cloudwatch_log_group" "eks_container_insights" {
  name  = "/aws/containerinsights/${aws_eks_cluster.main.name}/application"
  retention_in_days = 30
  kms_key_id  = aws_kms_key.log_key.arn

  tags = {
 Environment = "production"
 Service  = "eks"
  }
}

# amazon-cloudwatch-observability アドオン
resource "aws_eks_addon" "cloudwatch_observability" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "amazon-cloudwatch-observability"
  resolve_conflicts_on_create = "OVERWRITE"
  service_account_role_arn = aws_iam_role.cloudwatch_agent.arn

  tags = {
 Environment = "production"
  }
}

# Fluent Bit 用 IRSA (logs:PutLogEvents / logs:CreateLogStream)
resource "aws_iam_role" "cloudwatch_agent" {
  name = "eks-cloudwatch-agent-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = {
  Federated = aws_iam_openid_connect_provider.eks.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
  StringEquals = {
 "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub" =
"system:serviceaccount:amazon-cloudwatch:cloudwatch-agent"
 "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:aud" =
"sts.amazonaws.com"
  }
}
 }]
  })
}

resource "aws_iam_role_policy_attachment" "cloudwatch_agent_logs" {
  role = aws_iam_role.cloudwatch_agent.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}

AWS CLI によるアドオン状態確認

# amazon-cloudwatch-observability アドオンの状態を確認
aws eks describe-addon \
  --cluster-name my-eks-cluster \
  --addon-name amazon-cloudwatch-observability \
  --query 'addon.{status:status,version:addonVersion,role:serviceAccountRoleArn}' \
  --output table

# Fluent Bit DaemonSet の稼働確認
kubectl get daemonset -n amazon-cloudwatch

# Container Insights の Log Group を確認
aws logs describe-log-groups \
  --log-group-name-prefix "/aws/containerinsights/" \
  --query 'logGroups[*].{name:logGroupName,retention:retentionInDays}' \
  --output table

コンソール手順 (Container Insights 有効化)

  1. EKS コンソールで対象クラスターを開き、「アドオン」タブをクリックします。
  2. 「アドオンの取得」→「Amazon CloudWatch Observability」を選択します。
  3. IAM ロールに Fluent Bit 用 IRSA ロールを指定します。
  4. 「作成」をクリックすると Fluent Bit DaemonSet が自動デプロイされます。

クロスリンク: EKS クラスター構築と Karpenter の詳細は EKS Cluster + Karpenter Vol1 (WP:2217) を、IRSA による IAM 権限委譲の詳細は EKS IRSA Vol2 (WP:2228) を参照してください。Fluent Bit の転送設定は amazon-cloudwatch Namespace の ConfigMap fluent-bit-config で管理されており、フィルタリングルールのカスタマイズが可能です。


5-4. Terraform による Log Group 統合管理

3 サービスの Log Group を Terraform の for_each で一元管理することで、保存期間・KMS 暗号化・タグの統一を実現します。

# KMS キー (全 Log Group 共通暗号化)
resource "aws_kms_key" "log_key" {
  description = "CloudWatch Logs encryption key"
  enable_key_rotation  = true
  deletion_window_in_days = 7

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid = "Enable IAM User Permissions"
  Effect = "Allow"
  Principal = {
 AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
  }
  Action= "kms:*"
  Resource = "*"
},
{
  Sid = "Allow CloudWatch Logs"
  Effect = "Allow"
  Principal = { Service = "logs.ap-northeast-1.amazonaws.com" }
  Action= ["kms:Encrypt*", "kms:Decrypt*", "kms:ReEncrypt*",
  "kms:GenerateDataKey*", "kms:Describe*"]
  Resource = "*"
}
 ]
  })
}

locals {
  service_log_groups = {
 lambda = {
name  = "/aws/lambda/my-app"
retention_in_days = 30
service  = "lambda"
 }
 ecs = {
name  = "/ecs/my-service"
retention_in_days = 30
service  = "ecs"
 }
 eks_app = {
name  = "/aws/containerinsights/my-cluster/application"
retention_in_days = 30
service  = "eks"
 }
  }
}

resource "aws_cloudwatch_log_group" "services" {
  for_each = local.service_log_groups

  name  = each.value.name
  retention_in_days = each.value.retention_in_days
  kms_key_id  = aws_kms_key.log_key.arn

  tags = {
 Environment = "production"
 Service  = each.value.service
  }
}

for_each を使用することで、サービスの追加・変更が locals の編集だけで完結します。本番環境では kms_key_id による暗号化を必ず有効化してください。KMS キーポリシーに logs.ap-northeast-1.amazonaws.com への権限付与を忘れると、CloudWatch Logs からの暗号化書き込みが失敗します。


6. 高速クエリチューニング + コスト最適化

CloudWatch Logs を大規模運用する際に直面するのが、クエリの応答速度低下コスト膨張です。本章では、Log Group の Infrequent Access (IA) Tier への移行、サンプリング設定、保存期間の階層設計、クエリ並列度の制御、クエリ結果キャッシュの 5 つのアプローチで、クエリ高速化とコスト最適化を同時に実現する方法を解説します。

fig05: Log Group コスト最適化

6-1. Log Group Infrequent Access (IA) Tier による保存コスト削減

AWS は 2023 年に CloudWatch Logs の Infrequent Access (IA) Tier を一般提供開始しました。Standard Tier と IA Tier では、ログの保存コストとクエリコストに大きな差があります。

Standard vs IA コスト比較

項目Standard TierIA Tier
インジェストコスト$0.50/GB$0.25/GB
保存コスト$0.03/GB/月$0.0025/GB/月
Logs Insights クエリコスト$0.005/GB スキャン$0.0025/GB スキャン
Metrics Filter利用可能利用不可
サブスクリプションフィルター利用可能利用不可
Contributor Insights利用可能利用不可
推奨ユースケースアラーム連携・リアルタイム監視アーカイブ・コスト削減優先

IA Tier は Metrics Filter とサブスクリプションフィルターが使用できないため、アラーム連携が不要な Log Group (デバッグログ、アーカイブ用途) に適用するのが基本方針です。

Terraform による IA Tier 設定

# Standard Tier (Metrics Filter 利用 Log Group)
resource "aws_cloudwatch_log_group" "lambda_app_standard" {
  name  = "/aws/lambda/my-app"
  retention_in_days = 30
  log_group_class= "STANDARD"
  tags = {
 Environment = "production"
 Tier  = "standard"
  }
}

# IA Tier (アーカイブ・デバッグ用途 Log Group)
resource "aws_cloudwatch_log_group" "lambda_debug_ia" {
  name  = "/aws/lambda/my-app/debug"
  retention_in_days = 90
  log_group_class= "INFREQUENT_ACCESS"
  tags = {
 Environment = "production"
 Tier  = "infrequent-access"
  }
}

# ECS アクセスログ IA Tier (長期アーカイブ)
resource "aws_cloudwatch_log_group" "ecs_access_ia" {
  name  = "/ecs/my-service/access"
  retention_in_days = 365
  log_group_class= "INFREQUENT_ACCESS"
  tags = {
 Environment = "production"
 Tier  = "infrequent-access"
  }
}

AWS CLI による IA Tier 確認と設定

# IA Tier で新規 Log Group 作成
aws logs create-log-group \
  --log-group-name "/aws/lambda/my-app/debug" \
  --log-group-class "INFREQUENT_ACCESS"

# 全 Log Group の Tier と保存期間を確認
aws logs describe-log-groups \
  --query 'logGroups[*].{name:logGroupName,class:logGroupClass,retention:retentionInDays}' \
  --output table

コンソール手順

  1. CloudWatch → Logs → Log groups → 「Create log group」をクリックします。
  2. Log group name を入力します。
  3. Log class で「Infrequent Access」を選択します。
  4. 「Create」をクリックして完了します。

注意: 既存の Log Group の Log Class 変更はコンソール・CLI ともに不可です。IA Tier 適用には Log Group の削除と再作成が必要なため、本番移行は慎重に計画してください。


6-2. Log Sampling によるインジェストコスト削減

高頻度の Lambda 関数や ECS サービスでは、全ログを CloudWatch Logs に送信しない Sampling 設定がインジェストコスト削減に有効です。

Lambda Powertools Logger によるサンプリング設定

Lambda Powertools Logger の sampling_rate パラメータを使用すると、DEBUG レベルのログを一定割合でのみ送信できます。INFO 以上は全件送信しつつ、DEBUG のみ間引く設計が本番環境の標準パターンです。

import os
from aws_lambda_powertools import Logger

# 環境変数でサンプリングレートを制御 (0.0 〜 1.0)
logger = Logger(
 service="my-app",
 level="INFO",
 sampling_rate=float(os.environ.get("LOG_SAMPLING_RATE", "0.1"))
)

def lambda_handler(event, context):
 # sampling_rate=0.1 の場合、DEBUG ログは 10% のリクエストのみ送信
 logger.debug("デバッグ情報", extra={"event_id": event.get("id")})
 logger.info("処理開始", extra={"request_id": context.aws_request_id})
 return {"statusCode": 200}
# Lambda 関数の環境変数でサンプリングレートを管理
resource "aws_lambda_function" "my_app" {
  function_name = "my-app"
  handler = "app.lambda_handler"
  runtime = "python3.12"
  filename= "function.zip"
  role = aws_iam_role.lambda_exec.arn

  environment {
 variables = {
LOG_SAMPLING_RATE = "0.1"# 本番: 10% サンプリング
POWERTOOLS_SERVICE_NAME = "my-app"
LOG_LEVEL= "INFO"
 }
  }
}

ECS CloudWatch Agent によるログフィルタリング

ECS Fargate では CloudWatch Agent の設定ファイルで不要なログレベルを除外できます。

{
  "logs": {
 "log_stream_name": "{cluster_name}/{container_name}/{task_id}",
 "logs_collected": {
"files": {
  "collect_list": [
 {
"file_path": "/var/log/app/*.log",
"log_group_name": "/ecs/my-service",
"log_stream_name": "{hostname}",
"filters": [
  {
 "type": "include",
 "expression": "\"level\":\"(ERROR|WARN|INFO)\""
  }
]
 }
  ]
}
 }
  }
}

ベストプラクティス: ERROR / WARN / INFO は全件送信し、DEBUG のみサンプリングします。サンプリングレートは CloudWatch Logs コストメトリクスを監視しながら段階的に調整してください。


6-3. 保存期間階層設計によるストレージコスト最適化

Log Group ごとに適切な保存期間を設定することで、不要なストレージコストを削減できます。保存期間を設定していない Log Group は無期限保持となるため、必ず明示的に設定してください。

保存期間別ユースケース設計指針

保存期間推奨ユースケース主な対象ログ月次コスト目安 (100GB/月)
7 日開発・ステージング環境 / デバッグログ/aws/lambda/dev-* / ステージング ECS$0.21
30 日本番アプリケーションログ (直近アクセス)/aws/lambda/prod-* / ECS アクセスログ$0.90
90 日セキュリティ監査 / コンプライアンス要件CloudTrail / VPC Flow Logs$2.70
365 日法的保存義務 / 長期障害分析アーカイブ用途 (IA Tier 推奨)$0.09 (IA Tier)

Terraform による保存期間設計

locals {
  log_retention_days = {
 development = 7
 staging  = 14
 production  = 30
 security = 90
 archive  = 365
  }
}

# 本番 Lambda アプリケーションログ (30 日)
resource "aws_cloudwatch_log_group" "lambda_prod" {
  name  = "/aws/lambda/prod-my-app"
  retention_in_days = local.log_retention_days["production"]
  log_group_class= "STANDARD"
}

# セキュリティ監査ログ (90 日)
resource "aws_cloudwatch_log_group" "lambda_audit" {
  name  = "/aws/lambda/prod-my-app/audit"
  retention_in_days = local.log_retention_days["security"]
  log_group_class= "STANDARD"
}

# アーカイブログ (365 日 / IA Tier)
resource "aws_cloudwatch_log_group" "lambda_archive" {
  name  = "/aws/lambda/prod-my-app/archive"
  retention_in_days = local.log_retention_days["archive"]
  log_group_class= "INFREQUENT_ACCESS"
}

AWS CLI による保存期間設定と棚卸し

# 本番 Lambda Log Group に 30 日保存期間を設定
aws logs put-retention-policy \
  --log-group-name "/aws/lambda/prod-my-app" \
  --retention-in-days 30

# セキュリティ監査ログに 90 日保存期間を設定
aws logs put-retention-policy \
  --log-group-name "/aws/lambda/prod-my-app/audit" \
  --retention-in-days 90

# 保存期間未設定 (無期限) の Log Group を検出
aws logs describe-log-groups \
  --query 'logGroups[?!retentionInDays].{name:logGroupName,storedBytes:storedBytes}' \
  --output table

6-4. クエリ並列度制御とバッチ分割戦略

CloudWatch Logs Insights では、1 アカウント / リージョンあたりの同時実行クエリ数に上限があります。大規模なログ分析でこの上限に達すると、クエリが ThrottlingException エラーで失敗します。

同時実行クエリ上限

リソースデフォルト上限クォータ申請可否
同時実行 Logs Insights クエリ10 / アカウント / リージョン
クエリ結果の最大保持件数10,000 件不可
クエリ実行時間の上限60 分不可

バッチ分割戦略: Python SDK 実装例

複数の Log Group に対して順次クエリを実行し、スロットリングを回避するバッチ処理パターンです。

import boto3
import time
from datetime import datetime, timedelta

logs_client = boto3.client('logs', region_name='ap-northeast-1')

def batch_query_log_groups(log_group_names: list, query: str, hours_back: int = 24):
 """複数 Log Group をバッチ分割してクエリを順次実行します。"""
 end_time = int(datetime.utcnow().timestamp())
 start_time = int((datetime.utcnow() - timedelta(hours=hours_back)).timestamp())
 all_results = []

 # 5 グループ以下のバッチに分割して実行
 batch_size = 5
 for i in range(0, len(log_group_names), batch_size):
  batch = log_group_names[i:i + batch_size]
  response = logs_client.start_query(
logGroupNames=batch,
startTime=start_time,
endTime=end_time,
queryString=query,
limit=1000
  )
  query_id = response['queryId']

  # クエリ完了を最大 60 秒待機
  for _ in range(12):
result = logs_client.get_query_results(queryId=query_id)
if result['status'] in ('Complete', 'Failed', 'Cancelled'):
 break
time.sleep(5)

  all_results.extend(result.get('results', []))
  time.sleep(1)  # スロットリング回避のためバッチ間に 1 秒待機

 return all_results

AWS CLI によるクエリ状態確認とキャンセル

# 現在実行中のクエリ一覧を確認
aws logs describe-queries \
  --status Running \
  --query 'queries[*].{id:queryId,status:status,logGroup:logGroupName,createTime:createTime}' \
  --output table

# スロットリング回避のため不要なクエリをキャンセル
aws logs stop-query \
  --query-id "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# クォータ確認
aws service-quotas get-service-quota \
  --service-code logs \
  --quota-code L-7EE6B2A8

クォータ申請: 定期的な大規模分析を実施する場合は、AWS Service Quotas コンソールから「Concurrent CloudWatch Logs Insights queries」のクォータ増加を申請してください。


6-5. クエリ結果キャッシュ活用によるコスト節約

Logs Insights の GetQueryResults API はクエリ実行後 7 日間結果を保持します。同じ期間のログに対して同一クエリを繰り返し実行する場合、キャッシュ済みの query_id を再利用することでスキャンコストをゼロにできます。

query_id 再利用によるキャッシュ戦略

import boto3
import json
import hashlib
from datetime import datetime, timedelta

logs_client = boto3.client('logs', region_name='ap-northeast-1')
dynamodb = boto3.resource('dynamodb')
cache_table = dynamodb.Table('logs-query-cache')

def get_cached_or_run_query(log_group: str, query: str, start_time: int, end_time: int):
 """キャッシュ済みの query_id を再利用してスキャンコストをゼロにします。"""
 cache_key = hashlib.md5(
  f"{log_group}:{query}:{start_time}:{end_time}".encode()
 ).hexdigest()

 # DynamoDB でキャッシュ済みの query_id を確認
 cached = cache_table.get_item(Key={'cache_key': cache_key})
 if 'Item' in cached:
  query_id = cached['Item']['query_id']
  result = logs_client.get_query_results(queryId=query_id)
  if result['status'] == 'Complete':
return result['results']

 # 新規クエリ実行と query_id のキャッシュ保存
 import time
 response = logs_client.start_query(
  logGroupName=log_group,
  startTime=start_time,
  endTime=end_time,
  queryString=query
 )
 query_id = response['queryId']

 while True:
  result = logs_client.get_query_results(queryId=query_id)
  if result['status'] == 'Complete':
# query_id を DynamoDB にキャッシュ (TTL: 7 日)
cache_table.put_item(Item={
 'cache_key': cache_key,
 'query_id': query_id,
 'ttl': int(datetime.utcnow().timestamp()) + 7 * 86400
})
return result['results']
  time.sleep(2)

CloudWatch Dashboard ウィジェットの更新間隔最適化

CloudWatch Dashboard の Logs Insights ウィジェットに短すぎる自動更新間隔を設定すると、クエリコストが急増します。

# Dashboard を JSON で定義し、更新間隔を最適化
aws cloudwatch put-dashboard \
  --dashboard-name "production-logs-insights" \
  --dashboard-body '{
 "widgets": [
{
  "type": "log",
  "width": 24,
  "height": 6,
  "properties": {
 "query": "SOURCE \"/aws/lambda/prod-my-app\" | fields @timestamp, @message\n| stats count() by bin(5m)",
 "region": "ap-northeast-1",
 "title": "Lambda エラー件数 (5 分粒度)",
 "view": "timeSeries",
 "period": 300
  }
}
 ]
  }'

推奨設定: Dashboard の自動更新間隔は最小 5 分 (300 秒) に設定し、定期分析ダッシュボードは 1 時間 (3600 秒) を標準とします。不要なリアルタイム更新を排除するだけで Logs Insights クエリコストを 60〜80% 削減できるケースがあります。


QG-4: 高速クエリ + コスト最適化チェックリスト

  • Log Group Sampling: Lambda Powertools sampling_rate / ECS CloudWatch Agent filters で DEBUG ログのサンプリングを設定済みか。本番環境では ERROR / WARN / INFO 全件 + DEBUG は 10% 以下が目安です。未設定の場合は Lambda 関数の環境変数 LOG_SAMPLING_RATE=0.1 から段階的に導入してください。
  • IA (Infrequent Access) Tier: Metrics Filter・サブスクリプションフィルター不要の Log Group (アーカイブ・デバッグ用途) を log_group_class = "INFREQUENT_ACCESS" に移行済みか。インジェスト $0.25/GB・Insights クエリ $0.0025/GB スキャンで Standard Tier の約 50% 削減が可能です。既存 Log Group は削除→再作成が必要な点に注意してください。
  • 保存期間設計: 開発 7 日 / 本番アプリ 30 日 / セキュリティ監査 90 日 / アーカイブ 365 日の階層で各 Log Group に retention_in_days を設定済みか。aws logs describe-log-groups --query 'logGroups[?!retentionInDays]' で無期限保持の Log Group が残っていないか定期確認してください。
  • クエリ並列度: 同時実行 Logs Insights クエリ上限 (デフォルト 10 / アカウント / リージョン) を考慮し、大規模分析は 5 グループ以下のバッチ分割 + バッチ間 1 秒待機で実施しているか。定期分析が多い場合は AWS Service Quotas からクォータ増加申請を検討してください。
  • 結果キャッシュ: 同一期間・同一クエリを繰り返し実行する場合、GetQueryResults の 7 日保持を活用した query_id キャッシュ層 (DynamoDB + TTL) を導入済みか。CloudWatch Dashboard の自動更新間隔を最小 5 分 (period: 300) に設定し、定期分析ダッシュボードは 1 時間 (period: 3600) を標準として不要なクエリ実行を抑制してください。

7. ダッシュボード作成と運用 Runbook テンプレート集

CloudWatch Logs の観測性基盤を日常運用に定着させる鍵は、「使われるダッシュボード」と「すぐ実行できる Runbook」の整備です。本章では CloudWatch Dashboard の widget 種別と Terraform による自動管理方法を解説し、障害対応から定期点検まで網羅する Runbook テンプレート 5 種を提供します。

fig06: 観測性 Dashboard ウィジェット階層図 + 運用 Runbook フロー

7-1. CloudWatch Dashboard の widget 設計

CloudWatch Dashboard は 4 種類の widget で構成されます。Logs Insights クエリを埋め込む log 型と、Metrics Filter で生成したカスタムメトリクスを表示する metric 型が本記事の中核です。

widget 種別と用途

widget 型表示形式主な用途Logs Insights 利用
logテーブル / 時系列グラフエラーログ一覧・リクエスト分析✅ クエリ直接埋め込み
metric折れ線 / 棒グラフ / 数値Metrics Filter カスタムメトリクス❌ メトリクス参照
alarmアラーム状態インジケータAlarm 状態一覧❌ Alarm 参照
textMarkdown テキスト説明・手順リンク❌ 静的コンテンツ

効果的な Dashboard 構成の原則

ダッシュボードは「見る目的」で設計します。運用チームが使う Dashboard には以下の 3 レイヤー構成を推奨します。

  1. Top-level (全体健全性): alarm widget でサービス全体の Alarm 状態を一覧表示。異常があれば即座に発見できる構成。
  2. Service-level (サービス詳細): metric widget でエラーレート・レイテンシ・スループットを時系列グラフ化。Metrics Filter カスタムメトリクスを参照。
  3. Log-level (ログ分析): log widget で Logs Insights クエリを埋め込み、エラーログ一覧・P99 レイテンシ・5xx 件数を表示。

7-2. Logs Insights 埋め込みウィジェット (Terraform + CLI + コンソール)

Terraform による Dashboard 設定

resource "aws_cloudwatch_dashboard" "production_observability" {
  dashboard_name = "production-observability"

  dashboard_body = jsonencode({
 widgets = [
# Top-level: Alarm 状態一覧
{
  type= "alarm"
  x= 0
  y= 0
  width  = 24
  height = 3
  properties = {
 title  = "⚠️ アラーム状態一覧"
 alarms = [
"arn:aws:cloudwatch:ap-northeast-1:${var.account_id}:alarm:prod-lambda-error-rate",
"arn:aws:cloudwatch:ap-northeast-1:${var.account_id}:alarm:prod-ecs-5xx-count",
"arn:aws:cloudwatch:ap-northeast-1:${var.account_id}:alarm:prod-eks-pod-error-rate"
 ]
  }
},
# Service-level: Lambda エラーレート (Metrics Filter メトリクス)
{
  type= "metric"
  x= 0
  y= 3
  width  = 12
  height = 6
  properties = {
 title= "Lambda エラーレート (5 分粒度)"
 view = "timeSeries"
 stacked = false
 metrics = [
["MyApp/Lambda", "ErrorCount", { stat = "Sum", period = 300 }]
 ]
 region = "ap-northeast-1"
 period = 300
  }
},
# Log-level: Lambda エラーログ一覧 (Logs Insights 埋め込み)
{
  type= "log"
  x= 12
  y= 3
  width  = 12
  height = 6
  properties = {
 title= "Lambda エラーログ (直近 1 時間)"
 query= "SOURCE '/aws/lambda/prod-my-app' | fields @timestamp, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 100"
 region  = "ap-northeast-1"
 view = "table"
 period  = 3600
  }
},
# Log-level: ECS P99 レイテンシ (Logs Insights 時系列)
{
  type= "log"
  x= 0
  y= 9
  width  = 12
  height = 6
  properties = {
 title= "ECS P99 レイテンシ (5 分粒度)"
 query= "SOURCE '/ecs/my-service' | fields @timestamp, @message | parse @message '\"duration\":*,' as duration | stats pct(duration, 99) as p99 by bin(5m)"
 region  = "ap-northeast-1"
 view = "timeSeries"
 period  = 300
  }
}
 ]
  })
}

AWS CLI による Dashboard 設定確認

# Dashboard の JSON 定義を取得
aws cloudwatch get-dashboard \
  --dashboard-name "production-observability" \
  --query 'DashboardBody' \
  --output text | python3 -m json.tool

# Dashboard 一覧と更新日時を確認
aws cloudwatch list-dashboards \
  --query 'DashboardEntries[*].{name:DashboardName,modified:LastModified}' \
  --output table

コンソール手順

  1. CloudWatch → Dashboards → 「Create dashboard」をクリックします。
  2. Dashboard name を入力し「Create dashboard」をクリックします。
  3. 「Add widget」から widget 種別を選択します。Log 型 widget は「Logs query」を選択してください。
  4. クエリエディタに Logs Insights クエリを入力し、「Update widget」をクリックして保存します。
  5. Dashboard 画面右上の「Save」をクリックして Dashboard を保存します。

7-3. Terraform による QueryDefinition 管理

Logs Insights クエリを Terraform で管理すると、クエリのバージョン管理・レビュー・共有が容易になります。

# Lambda エラー分析クエリ
resource "aws_cloudwatch_query_definition" "lambda_error_analysis" {
  name = "prod/lambda/error-analysis"

  log_group_names = [
 "/aws/lambda/prod-my-app"
  ]

  query_string = <<-EOT
 fields @timestamp, @message, @requestId
 | filter @message like /ERROR/
 | parse @message '[*] *' as level, detail
 | stats count(*) as error_count by bin(5m)
 | sort @timestamp desc
  EOT
}

# ECS 5xx カウントクエリ
resource "aws_cloudwatch_query_definition" "ecs_5xx_count" {
  name = "prod/ecs/5xx-count"

  log_group_names = [
 "/ecs/my-service"
  ]

  query_string = <<-EOT
 fields @timestamp, @message
 | parse @message '"status":*,' as status
 | filter status >= 500
 | stats count(*) as count_5xx by bin(5m)
 | sort @timestamp desc
  EOT
}

# EKS Pod エラー横断検索クエリ
resource "aws_cloudwatch_query_definition" "eks_pod_errors" {
  name = "prod/eks/pod-errors"

  log_group_names = [
 "/aws/eks/prod-cluster/application"
  ]

  query_string = <<-EOT
 fields @timestamp, kubernetes.pod_name, @message
 | filter @message like /(?i)(error|exception|fatal)/
 | stats count(*) as error_count by kubernetes.pod_name
 | sort error_count desc
 | limit 20
  EOT
}

7-4. 運用 Runbook テンプレート 5 種

本番運用チームがすぐに採用できる Runbook テンプレートを 5 種提供します。各テンプレートは「起動トリガ / 目的 / Logs Insights クエリ / Metrics Filter 参照 / Dashboard 連携」の 4 軸で構造化しています。

Runbook 1: 障害切り分け (Incident Triage)

項目内容
起動トリガCloudWatch Alarm 発報 / SLO 違反検知
目的エラー発生範囲と根本原因の特定 (平均 5 分以内)
Logs Insights クエリfilter @message like /ERROR/ \| stats count(*) by @logStream \| sort count(*) desc
Metrics FilterErrorCount メトリクスで時系列グラフを確認し、エラー急増の開始時刻を特定
Dashboard 連携production-observability ダッシュボードの「Lambda エラーログ一覧」widget で詳細確認
# 障害発生時の初動クエリ (過去 30 分)
aws logs start-query \
  --log-group-names "/aws/lambda/prod-my-app" "/ecs/my-service" \
  --start-time $(date -v-30M +%s) \
  --end-time $(date +%s) \
  --query-string "fields @timestamp, @logStream, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 50" \
  --region ap-northeast-1

Runbook 2: リクエスト追跡 (Request Tracing)

項目内容
起動トリガユーザーからの特定リクエストID / トランザクションID に関する問い合わせ
目的特定リクエストの処理フローを Lambda → ECS → EKS の全サービスで追跡
Logs Insights クエリfilter @message like /REQUEST_ID/ \| fields @timestamp, @logStream, @message \| sort @timestamp asc
Metrics Filter対象なし (個別リクエスト追跡は Logs Insights で対応)
Dashboard 連携Vol2 (X-Ray+ADOT) の サービスマップと連携してトレースを補完
# requestId でログを横断検索
REQUEST_ID="xxxx-yyyy-zzzz"
aws logs start-query \
  --log-group-names "/aws/lambda/prod-my-app" \
  --start-time $(date -v-1d +%s) \
  --end-time $(date +%s) \
  --query-string "fields @timestamp, @requestId, @message | filter @requestId = '${REQUEST_ID}' | sort @timestamp asc"

Runbook 3: コスト監視 (Cost Monitoring)

項目内容
起動トリガ月次定期実施 / ログコスト増加アラート
目的Log Group 別のインジェスト量と保存量を把握し、コスト最適化候補を特定
Logs Insights クエリstats sum(@ingestionSize) as total_bytes by @logGroup \| sort total_bytes desc \| limit 20
Metrics Filter対象なし (コスト分析は CLI コマンドを使用)
Dashboard 連携production-observability ダッシュボードにコスト監視 widget を追加して可視化
# Log Group 別インジェスト量 TOP 10 (直近 7 日)
aws logs start-query \
  --log-group-names "/aws/lambda/prod-my-app" "/ecs/my-service" "/aws/eks/prod-cluster/application" \
  --start-time $(date -v-7d +%s) \
  --end-time $(date +%s) \
  --query-string "stats sum(@ingestionSize) as total_bytes by @logGroup | sort total_bytes desc | limit 10"

# 保存期間未設定の Log Group を検出
aws logs describe-log-groups \
  --query 'logGroups[?!retentionInDays].{name:logGroupName,storedBytes:storedBytes}' \
  --output table

Runbook 4: SLO 違反対応 (SLO Violation Response)

項目内容
起動トリガエラーバジェット枯渇警告 / 可用性 SLO 違反
目的SLO 違反の原因特定と影響範囲の定量化
Logs Insights クエリfilter @message like /5[0-9][0-9]/ \| stats count(*) as error_count, count(*) / 1000 * 100 as error_rate by bin(1h)
Metrics Filter5xxCount メトリクスで時系列グラフを確認し、違反期間のエラーレートを算出
Dashboard 連携Vol3 (Application Signals) の SLO ダッシュボードと連携してエラーバジェット消費を可視化
# 直近 24 時間の 5xx エラーレート算出
aws logs start-query \
  --log-group-names "/ecs/my-service" \
  --start-time $(date -v-1d +%s) \
  --end-time $(date +%s) \
  --query-string "parse @message '\"status\":*,' as status | stats count(status >= 500) as errors, count(*) as total, errors / total * 100 as error_rate_pct by bin(1h) | sort @timestamp desc"

Runbook 5: アラーム棚卸 (Alarm Audit)

項目内容
起動トリガ四半期定期実施 / チームレビュー
目的不要・過剰・閾値不適切なアラームの整理と最適化
Logs Insights クエリ対象なし (CloudWatch Alarm API を使用)
Metrics Filter各 filter の filterPattern と閾値の有効性を確認
Dashboard 連携production-observability ダッシュボードの Alarm widget で全アラーム状態を確認
# OK 状態が続いているアラームを確認 (閾値見直し候補)
aws cloudwatch describe-alarms \
  --state-value OK \
  --query 'MetricAlarms[*].{name:AlarmName,threshold:Threshold,state:StateValue,reason:StateReason}' \
  --output table

# INSUFFICIENT_DATA 状態のアラームを検出 (設定誤りや未使用メトリクス)
aws cloudwatch describe-alarms \
  --state-value INSUFFICIENT_DATA \
  --query 'MetricAlarms[*].{name:AlarmName,namespace:Namespace,metric:MetricName}' \
  --output table

# Metrics Filter 一覧の棚卸し
aws logs describe-metric-filters \
  --query 'metricFilters[*].{group:logGroupName,filter:filterName,pattern:filterPattern,namespace:metricTransformations[0].metricNamespace}' \
  --output table

Runbook 運用のベストプラクティス

Runbook は「最初の 1 種から始めて段階的に拡大」することを推奨します。まず Runbook 1 (障害切り分け) を チームの Wiki または Confluence に登録し、障害発生時に実際に使いながら改善してください。Runbook の定着後に Runbook 2 (リクエスト追跡) → Runbook 3 (コスト監視) の順で拡張すると、チームへの負荷が最小限になります。

QG-5: ダッシュボード + 運用 Runbook チェックリスト

  • Dashboard 構成 (3 レイヤー): Top-level (alarm widget でアラーム状態一覧) / Service-level (metric widget でエラーレート・レイテンシ) / Log-level (log widget で Logs Insights クエリ埋め込み) の 3 レイヤーで Dashboard が構成されているか。特に Log-level widget の自動更新間隔が最小 5 分 (period: 300) になっているか確認してください。
  • QueryDefinition Terraform 管理: 本番頻出クエリが aws_cloudwatch_query_definition で Terraform 管理されているか。クエリをコンソールだけで管理すると変更履歴が失われ、チーム共有もできません。最低でも障害切り分け・リクエスト追跡・コスト監視の 3 クエリを Terraform で管理してください。
  • Runbook 最低 1 種の整備: チームの Wiki / Confluence に Runbook が少なくとも 1 種 (障害切り分け) 登録されているか。登録先 URL を Dashboard の text widget に掲載し、障害対応中でも即座にアクセスできる状態にしてください。
  • Runbook の定期レビュー: Runbook 5 (アラーム棚卸) を四半期に 1 回実施し、INSUFFICIENT_DATA 状態のアラームや OK 状態が 90 日以上続いているアラームを閾値見直しまたは削除の候補として検討しているか。アラームの過検知・見逃しはどちらも SRE チームの疲弊につながります。
  • SLO 連携準備: Vol3 (CloudWatch Application Signals + Synthetics) に向けて、本記事で設定した Metrics Filter カスタムメトリクス (ErrorCount / 5xxCount / P99Latency 等) の namespace と metric 名を記録しているか。Application Signals の SLO 設定時にこれらのメトリクスを参照します。

8. まとめ + 落とし穴 10 選 + Vol2/3 予告

本章では、記事全体の要点をチートシートとしてまとめ、実際の本番環境で頻繁に遭遇する落とし穴 10 選を解説します。最後に観測性 3 部作の続刊予告と AWS 本番運用シリーズ 9 巻への誘導リンクを掲載します。

8-1. Logs Insights クエリ言語チートシート

コマンド構文代表ユースケース
fieldsfields @timestamp, @message返却フィールドの選択
filterfilter @message like /ERROR/条件でログを絞り込み
statsstats count(*) by bin(5m)集計 (count / sum / avg / pct)
sortsort @timestamp desc結果の並び替え
limitlimit 100返却件数の制限
parseparse @message '"status":*,' as statusフィールド抽出
displaydisplay @timestamp, status表示フィールドの選択 (filter 後)
dedupdedup @requestId重複排除

本番頻出パターン TOP 5

# 1. エラー件数を 5 分粒度で時系列集計
fields @timestamp, @message
| filter @message like /ERROR/
| stats count(*) as error_count by bin(5m)
| sort @timestamp desc

# 2. Lambda cold start を検出して平均 initDuration を算出
filter @type = "REPORT" and @initDuration > 0
| stats avg(@initDuration) as avg_cold_start_ms, count(*) as count by bin(1h)

# 3. ECS 5xx レスポンスを status コードで集計
parse @message '"status":*,' as status
| filter status >= 500
| stats count(*) as count by status
| sort count desc

# 4. JSON ログから latency フィールドを抽出して P99 を算出
parse @message '{"latency":*,' as latency
| stats pct(latency, 99) as p99, pct(latency, 95) as p95 by bin(5m)

# 5. EKS Pod 別エラー件数ランキング (上位 20 Pod)
filter @message like /(?i)error/
| stats count(*) as error_count by kubernetes.pod_name
| sort error_count desc
| limit 20

Metrics Filter filter pattern クイックリファレンス

用途filter pattern形式
HTTP 5xx エラー{ $.status >= 500 }JSON
Lambda ERROR ログ (Powertools){ $.level = "ERROR" }JSON
高レイテンシ検出 (1000ms 超){ $.latency > 1000 }JSON
Lambda Cold Start 検出REPORT Init DurationSpace-delimited
IAM 認証エラー{ $.errorCode = "UnauthorizedException" }JSON
ECS コンテナ OOMOutOfMemoryErrorSpace-delimited

8-2. 落とし穴 10 選

実際の本番環境で頻繁に遭遇する落とし穴をまとめます。

  1. IA Tier に移行した Log Group で Metrics Filter が動作しなくなる: IA Tier は Metrics Filter・サブスクリプションフィルター・Contributor Insights をサポートしません。移行前に Metrics Filter 設定の有無を必ず確認してください。

  2. Log Group のデフォルト Log Class は STANDARD のみ: 既存 Log Group の log_group_class は後から変更できません。IA Tier への移行は Log Group の削除と再作成が必要です。本番移行時は新しい Log Group 名でのデータ移行計画を立ててください。

  3. Logs Insights クエリは最大 10,000 件しか返却しない: stats の集計結果も 10,000 件が上限です。全件取得が必要な場合は時間範囲を分割してクエリを実行し、結果をマージしてください。

  4. filter より parse が先に評価される: Logs Insights のクエリ評価順は parsefilterstats の順です。filter でログを絞り込んでから parse を実行するように見えても、内部的には parse が先に評価されるため、parse でエラーになるログは filter でも排除されません。

  5. Metrics Filter は Log Group 作成後に設定しなければならない: Terraform では aws_cloudwatch_log_groupdepends_onaws_cloudwatch_log_metric_filter が Log Group 作成後に作られるよう順序を制御してください。

  6. filter pattern の % はワイルドカード、. は任意 1 文字ではない: Space-delimited pattern での % はワイルドカードですが、JSON / 正規表現モードでは .* を使用します。Space-delimited と JSON / 正規表現のモード切替を意識してください。

  7. Dashboard の log widget クエリは自動更新のたびに課金される: 自動更新間隔が 1 分に設定されている場合、1 時間で 60 クエリ = $0.005 × スキャン GB が課金されます。最低でも 5 分 (period: 300) に設定してください。

  8. aws_cloudwatch_query_definitionlog_group_names は任意だが省略すると全 Log Group が対象: クエリエディタでの表示は変わりませんが、Log Group を指定しないと誤って全ログをスキャンするリスクがあります。本番環境では必ず log_group_names を指定してください。

  9. SNS Topic の Email サブスクリプションは確認メール承認が必要: Terraform で aws_sns_topic_subscription を作成しても、Email エンドポイントは受信者がメールの確認リンクをクリックするまで PendingConfirmation 状態が続きます。アラームテスト前に確認メールの承認を済ませてください。

  10. CloudWatch Alarm の treat_missing_data のデフォルトは missing: データなし期間 (Lambda 関数が未起動の夜間等) では Alarm 状態が INSUFFICIENT_DATA となり、誤報の原因となります。回避策として treat_missing_data = "notBreaching" を設定し、データなし期間を正常扱いとする運用を検討してください。

8-3. 観測性 3 部作と AWS 本番運用シリーズ 9 巻のご案内

本記事 (Vol1) でログ層の可視化基盤が整いました。続く Vol2・Vol3 でトレース・SLO 層を整備し、AWS 本番運用シリーズ 9 巻体制を完成させてください。

観測性 3 部作 全体ロードマップ

タイトルキー技術本記事との接続点
Vol1 (本記事) ✅Logs Insights × Metrics FilterCloudWatch Logs / Metrics Filter / Dashboardログ層の可視化基盤を確立
Vol2 ✅X-Ray + ADOT 分散トレースAWS X-Ray / ADOT Collector / ServiceLensVol1 の Metrics Filter アラームを X-Ray サービスマップと連携
Vol3 (近日公開)Application Signals + SyntheticsSLO / エラーバジェット / CanaryVol1 のカスタムメトリクスを SLO 指標として Application Signals に登録

Vol2 (X-Ray + ADOT 分散トレース完全活用編) で実現すること

Vol2 では本記事で設定した CloudWatch Logs の構造化ログ (Lambda Powertools / Fluent Bit) を X-Ray トレースと相関分析し、「ログから該当トレースへのジャンプ」による高速障害調査を実現します。

  • ADOT Collector Terraform 実装: Lambda Layer / ECS サイドカー / EKS DaemonSet の 3 形態で
    ADOT Collector をデプロイし、トレースデータを X-Ray に送信する設定を一気通貫で実装します。
  • CloudWatch ServiceLens 統合: X-Ray サービスマップ上で Lambda / ECS / EKS のエラーレートと
    レイテンシを可視化。本記事の Metrics Filter カスタムメトリクスを ServiceLens に追加する手順を解説。
  • サンプリングレート設計: 本番での X-Ray サンプリングルールを Terraform aws_xray_sampling_rule
    で管理し、高トラフィック環境でのコスト最適化とトレース品質のバランス調整指針を提示します。
  • ログ・トレース相関クエリ: Logs Insights の @requestId と X-Ray の traceId を結合し、
    1 つの API リクエストのログとトレースを横断検索するクエリパターンを提供します。

Vol3 (CloudWatch Application Signals + Synthetics SLO 駆動監視編) で実現すること

Vol3 では本記事の Metrics Filter カスタムメトリクス (ErrorCount / 5xxCount / P99Latency 等) を Application Signals の SLO 指標として登録し、エラーバジェット駆動の本番監視体制を整備します。

  • SLO / エラーバジェット設計: Application Signals で可用性 99.9% / レイテンシ P99 < 500ms の
    2 軸 SLO を定義し、月次エラーバジェット (許容ダウン時間 43 分) の自動計算を設定します。
  • CloudWatch Synthetics Canary: Lambda で実行するエンドポイントヘルスチェック Canary を
    Terraform aws_synthetics_canary で作成し、SLO 計算に組み込む手順を解説します。
  • Alarm 階層設計: L1 (個別サービス) / L2 (SLO 違反) / L3 (コンポジット) の 3 階層で
    CloudWatch Alarm を設計し、アラートノイズを最小化する本番構成を提示します。
  • エラーバジェット枯渇時の自動対応: バジェットが閾値を下回った際に SNS → Lambda →
    Slack/PagerDuty 通知を自動化するパイプラインを Terraform で実装する手順を含みます。

前提シリーズ: AWS 本番運用シリーズ 9 巻 (既読推奨)

シリーズVol1Vol2Vol3
Lambda 本番運用Container Image デプロイSnapStart 応用Powertools+Layers
EKS 本番運用Cluster+KarpenterIRSAALB+Argo CD
観測性 (本シリーズ)本記事 ✅X-Ray + ADOT ✅Application Signals SLO ✅

本記事で構築した Logs Insights クエリセット・Metrics Filter アラーム・Dashboard・Runbook テンプレートを土台に、Vol2 では X-Ray トレースとの相関分析で「ログ → トレース」を横断する障害調査を、Vol3 では Application Signals の SLO 設定で「エラーバジェット駆動の運用」を実現します。

Lambda 本番運用 3 部作 (Vol1 / Vol2 / Vol3) および EKS 本番運用 3 部作 (Vol1 / Vol2 / Vol3) で構築したワークロードに対し、観測性 3 部作で完全な可観測性レイヤーを後付けで実現することが、9 巻シリーズの設計思想です。

前提シリーズ: Lambda 本番運用 Vol1 を読む

次を読む: 観測性Vol2 X-Ray + ADOT 分散トレーシング基盤

次を読む: 観測性Vol3 Application Signals + SLO 完結巻