- 1 1. この記事について
- 2 2. 前提・環境・準備
- 3 3. CloudWatch Logs Insights 基礎・クエリ言語徹底解説
- 4 4. Metrics Filter によるメトリクス抽出とアラーム連携
- 5 5. Lambda/ECS/EKS 各サービスからのログ集約パターン
- 6 6. 高速クエリチューニング + コスト最適化
- 7 7. ダッシュボード作成と運用 Runbook テンプレート集
- 8 8. まとめ + 落とし穴 10 選 + Vol2/3 予告
1. この記事について

- 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 階層設計
- 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 件の差別化領域として確認されました。
- Log Group IA Tier × Sampling × 保存期間設計のコスト最適化を Terraform 完全実装で提示:
言及のみ (実装手順なし) の記事が 1 件あったのみ。IA Tier の Standard 比約 50% 削減効果を
Terraform 実装ハンズオンと合わせて解説した記事は存在しなかった。 - 運用 Runbook テンプレート 5 種 (障害切り分け / リクエスト追跡 / コスト監視 / SLO 違反対応 / アラーム棚卸):
Logs Insights クエリや Dashboard 設計を Runbook 形式でドキュメント化した記事は 0 件。
本番運用チームがすぐに採用できる形式での提示は存在しなかった。 - 3 サービス × 4 軸比較マトリクス (ログドライバ / 集約先 / Container Insights 統合 / コスト):
Lambda / ECS / EKS を横断的に扱った記事は競合 10 件中 0 件。 - Terraform 完全 IaC 化による全リソース管理:
CloudWatch コンソール操作のみで設定・変更するケースが多数の中、本記事では Logs Insights クエリ定義・
Metrics Filter・CloudWatch Alarm・SNS Topic・Dashboard の全リソースを Terraformaws_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 権限 / 環境確認 | — |
| §3 | Logs Insights クエリ言語徹底解説 | 8 コマンド + 正規表現 + 複合クエリ | QG-1 brc-red |
| §4 | Metrics Filter + アラーム連携 | filter pattern + JSON 抽出 + Alarm + SNS | QG-2 brc-red |
| §5 | Lambda/ECS/EKS ログ集約パターン | 3 サービス別設定 + Container Insights 統合 | QG-3 brc-yellow |
| §6 | 高速クエリ + コスト最適化 | IA Tier + Sampling + 保存期間 + クエリキャッシュ | QG-4 brc-yellow |
| §7 | ダッシュボード + 運用 Runbook | Dashboard 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_group | CreateLogGroup | retention_in_days / log_group_class | §2, §5, §6 |
aws_cloudwatch_query_definition | PutQueryDefinition | query_string / log_group_names | §3, §7 |
aws_cloudwatch_log_metric_filter | PutMetricFilter | filter_pattern / metric_transformation | §4 |
aws_cloudwatch_metric_alarm | PutMetricAlarm | threshold / treat_missing_data | §4 |
aws_sns_topic | CreateTopic | name / tags | §4 |
aws_sns_topic_subscription | Subscribe | protocol / endpoint | §4 |
aws_cloudwatch_dashboard | PutDashboard | dashboard_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.nameをlog_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 CLI | v2.15 以上 | aws --version |
| Terraform | v1.7 以上 | terraform version |
| Python | 3.10 以上 (バッチスクリプト用) | python3 --version |
| kubectl | v1.29 以上 (§5 EKS 部分のみ) | kubectl version --client |
| jq | 1.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 による保存管理まで一気通貫で説明します。

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 users | DAU/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
コンソールでの操作手順:
- CloudWatch → ログのインサイト を開きます。
- ロググループ選択フィールドで対象 Log Group を 1 つ以上選択します(複数 Log Group の横断クエリも可能です)。
- クエリエディタに Logs Insights QL を入力します。
- 時間範囲を選択し、クエリの実行 をクリックします。
stats ... by bin(N)形式のクエリは 折れ線グラフ タブで時系列グラフとして表示できます。- 再利用するクエリは クエリを保存 から名前をつけて保存し、クエリライブラリ から呼び出せます。
| コマンド | ユースケース 1: エラー分析 | ユースケース 2: レイテンシ集計 | ユースケース 3: リソース追跡 |
|---|---|---|---|
| fields | fields @timestamp, level, message — エラーメッセージと時刻を返却 | fields @timestamp, responseTime, service — レイテンシフィールドを選択 | fields @timestamp, @requestId, @memorySize, @maxMemoryUsed — Lambda リソース使用量確認 |
| filter | filter level = "ERROR" — ERROR ログのみ絞り込み | filter ispresent(responseTime) and service = "api" — 対象サービス + 値存在確認 | filter @maxMemoryUsed / @memorySize > 0.9 — メモリ使用率 90% 超のみ |
| stats | stats count() as errors by bin(5m) — 5 分粒度エラー件数 | stats pct(responseTime, 99) as p99 by bin(5m) — 5 分粒度 P99 レイテンシ | stats avg(@maxMemoryUsed) by functionName — 関数別平均メモリ使用量 |
| sort | sort errors desc — エラー件数の多い順 | sort p99 desc — P99 の高い時間帯を上位表示 | sort avg_memory desc — メモリ消費の多い関数を上位へ |
| parse | parse @message /\[ERROR\] (?<reqId>\S+)/ — requestId 抽出 | parse @message '"responseTime":*,' as rt — glob でレイテンシ抽出 | parse @message '"traceId":"*"' as traceId — X-Ray traceId 抽出 |
| display | display @timestamp, level, message, requestId — 必要フィールドのみ表示 | display @timestamp, p50_ms, p95_ms, p99_ms — パーセンタイル列のみ表示 | display functionName, avg_memory, req_count — サマリービューを最小化 |
| limit | limit 100 — エラー上位 100 件取得 | limit 1000 — 長期間クエリの件数上限設定 | limit 30 — Pod 上位 30 件でダッシュボード表示 |
| dedup | dedup @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 によるメトリクス抽出とアラーム連携

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 体系:
| サービス | namespace | metric_name 例 |
|---|---|---|
| Lambda | CustomMetrics/Lambda | ErrorCount, SlowInvocationCount, TimeoutCount |
| ECS | CustomMetrics/ECS | TaskErrorCount, Http5xxCount, ResponseTimeP99 |
| EKS | CustomMetrics/EKS | PodErrorCount, ApiServerErrorCount, DBConnectionError |
| 共通 | CustomMetrics/Application | CriticalAlertCount, AuthFailureCount |
命名 3 原則:
- サービス別に namespace を分ける —
CustomMetrics/LambdaとCustomMetrics/ECSを混在させない - metric_name は動作 + 計測対象形式 —
ErrorCount/LatencyMs/ThrottleCountなど動詞 + 名詞形式で統一 - 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. コンソールでの設定手順
- CloudWatch → ロググループ を開き対象 Log Group を選択する
- アクション → メトリクスフィルターの作成 をクリックする
- filter pattern を入力し、パターンのテスト でサンプルログを貼り付けてマッチ動作を確認する
- 次のステップ → 名前空間
CustomMetrics/Lambda、メトリクス名ErrorCount、単位Countを入力する - フィルターの作成 で完了する
パターンのテスト機能を使うと、実際のログ文字列を貼り付けてマッチするかを事前確認できます。Alarm は CloudWatch → アラーム → アラームの作成 → メトリクスを選択 → CustomMetrics/Lambda → ErrorCount の手順で設定します。
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 への重複通知を排除し、アラームストームを防げます。
■ 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 に
EnvironmentとFunctionName(または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 形式でログ集約基盤を実装します。

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メトリクス集約"]
| サービス | ログドライバ / 収集方式 | 集約先 | Container Insights 統合 | コスト目安 |
|---|---|---|---|---|
| Lambda | CloudWatch Logs ネイティブ統合 (実行ロールで自動収集) | /aws/lambda/<function-name> | Lambda Insights アドオン(layers 追加)で統合可能 | インジェスト: $0.50/GB Insights クエリ: $0.005/GB スキャン |
| ECS Fargate | awslogs ドライバ ( logConfiguration / awslogs-group) | /ecs/<service-name>(任意設定) | CloudWatch Agent サイドカー経由でクラスター単位に有効化 | インジェスト: $0.50/GB awslogs ドライバ自体は無料 |
| EKS | Fluent 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 では、タスク定義の logConfiguration で awslogs ドライバを指定することで、コンテナの 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 ドライバ設定)
- ECS コンソール → 「タスク定義」→「新しいタスク定義の作成」をクリックします。
- コンテナ設定画面の「ログ設定」で「Amazon CloudWatch」を選択します。
- ロググループに
/ecs/<service-name>を入力します。 - ストリームプレフィックスに
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 有効化)
- EKS コンソールで対象クラスターを開き、「アドオン」タブをクリックします。
- 「アドオンの取得」→「Amazon CloudWatch Observability」を選択します。
- IAM ロールに Fluent Bit 用 IRSA ロールを指定します。
- 「作成」をクリックすると Fluent Bit DaemonSet が自動デプロイされます。
クロスリンク: EKS クラスター構築と Karpenter の詳細は EKS Cluster + Karpenter Vol1 (WP:2217) を、IRSA による IAM 権限委譲の詳細は EKS IRSA Vol2 (WP:2228) を参照してください。Fluent Bit の転送設定は
amazon-cloudwatchNamespace の ConfigMapfluent-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 つのアプローチで、クエリ高速化とコスト最適化を同時に実現する方法を解説します。

6-1. Log Group Infrequent Access (IA) Tier による保存コスト削減
AWS は 2023 年に CloudWatch Logs の Infrequent Access (IA) Tier を一般提供開始しました。Standard Tier と IA Tier では、ログの保存コストとクエリコストに大きな差があります。
Standard vs IA コスト比較
| 項目 | Standard Tier | IA 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
コンソール手順
- CloudWatch → Logs → Log groups → 「Create log group」をクリックします。
- Log group name を入力します。
- Log class で「Infrequent Access」を選択します。
- 「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% 削減できるケースがあります。
- Log Group Sampling: Lambda Powertools
sampling_rate/ ECS CloudWatch Agentfiltersで 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 種を提供します。

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 参照 |
text | Markdown テキスト | 説明・手順リンク | ❌ 静的コンテンツ |
効果的な Dashboard 構成の原則
ダッシュボードは「見る目的」で設計します。運用チームが使う Dashboard には以下の 3 レイヤー構成を推奨します。
- Top-level (全体健全性):
alarmwidget でサービス全体の Alarm 状態を一覧表示。異常があれば即座に発見できる構成。 - Service-level (サービス詳細):
metricwidget でエラーレート・レイテンシ・スループットを時系列グラフ化。Metrics Filter カスタムメトリクスを参照。 - Log-level (ログ分析):
logwidget で 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
コンソール手順
- CloudWatch → Dashboards → 「Create dashboard」をクリックします。
- Dashboard name を入力し「Create dashboard」をクリックします。
- 「Add widget」から widget 種別を選択します。Log 型 widget は「Logs query」を選択してください。
- クエリエディタに Logs Insights クエリを入力し、「Update widget」をクリックして保存します。
- 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 Filter | ErrorCount メトリクスで時系列グラフを確認し、エラー急増の開始時刻を特定 |
| 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 Filter | 5xxCount メトリクスで時系列グラフを確認し、違反期間のエラーレートを算出 |
| 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 (コスト監視) の順で拡張すると、チームへの負荷が最小限になります。
- 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 クエリ言語チートシート
| コマンド | 構文 | 代表ユースケース |
|---|---|---|
fields | fields @timestamp, @message | 返却フィールドの選択 |
filter | filter @message like /ERROR/ | 条件でログを絞り込み |
stats | stats count(*) by bin(5m) | 集計 (count / sum / avg / pct) |
sort | sort @timestamp desc | 結果の並び替え |
limit | limit 100 | 返却件数の制限 |
parse | parse @message '"status":*,' as status | フィールド抽出 |
display | display @timestamp, status | 表示フィールドの選択 (filter 後) |
dedup | dedup @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 Duration | Space-delimited |
| IAM 認証エラー | { $.errorCode = "UnauthorizedException" } | JSON |
| ECS コンテナ OOM | OutOfMemoryError | Space-delimited |
8-2. 落とし穴 10 選
実際の本番環境で頻繁に遭遇する落とし穴をまとめます。
IA Tier に移行した Log Group で Metrics Filter が動作しなくなる: IA Tier は Metrics Filter・サブスクリプションフィルター・Contributor Insights をサポートしません。移行前に Metrics Filter 設定の有無を必ず確認してください。
Log Group のデフォルト Log Class は STANDARD のみ: 既存 Log Group の
log_group_classは後から変更できません。IA Tier への移行は Log Group の削除と再作成が必要です。本番移行時は新しい Log Group 名でのデータ移行計画を立ててください。Logs Insights クエリは最大 10,000 件しか返却しない:
statsの集計結果も 10,000 件が上限です。全件取得が必要な場合は時間範囲を分割してクエリを実行し、結果をマージしてください。filterよりparseが先に評価される: Logs Insights のクエリ評価順はparse→filter→statsの順です。filterでログを絞り込んでからparseを実行するように見えても、内部的にはparseが先に評価されるため、parseでエラーになるログはfilterでも排除されません。Metrics Filter は Log Group 作成後に設定しなければならない: Terraform では
aws_cloudwatch_log_groupのdepends_onでaws_cloudwatch_log_metric_filterが Log Group 作成後に作られるよう順序を制御してください。filter pattern の
%はワイルドカード、.は任意 1 文字ではない: Space-delimited pattern での%はワイルドカードですが、JSON / 正規表現モードでは.*を使用します。Space-delimited と JSON / 正規表現のモード切替を意識してください。Dashboard の
logwidget クエリは自動更新のたびに課金される: 自動更新間隔が 1 分に設定されている場合、1 時間で 60 クエリ = $0.005 × スキャン GB が課金されます。最低でも 5 分 (period: 300) に設定してください。aws_cloudwatch_query_definitionのlog_group_namesは任意だが省略すると全 Log Group が対象: クエリエディタでの表示は変わりませんが、Log Group を指定しないと誤って全ログをスキャンするリスクがあります。本番環境では必ずlog_group_namesを指定してください。SNS Topic の Email サブスクリプションは確認メール承認が必要: Terraform で
aws_sns_topic_subscriptionを作成しても、Email エンドポイントは受信者がメールの確認リンクをクリックするまでPendingConfirmation状態が続きます。アラームテスト前に確認メールの承認を済ませてください。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 Filter | CloudWatch Logs / Metrics Filter / Dashboard | ログ層の可視化基盤を確立 |
| Vol2 ✅ | X-Ray + ADOT 分散トレース | AWS X-Ray / ADOT Collector / ServiceLens | Vol1 の Metrics Filter アラームを X-Ray サービスマップと連携 |
| Vol3 (近日公開) | Application Signals + Synthetics | SLO / エラーバジェット / Canary | Vol1 のカスタムメトリクスを 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 を
Terraformaws_synthetics_canaryで作成し、SLO 計算に組み込む手順を解説します。 - Alarm 階層設計: L1 (個別サービス) / L2 (SLO 違反) / L3 (コンポジット) の 3 階層で
CloudWatch Alarm を設計し、アラートノイズを最小化する本番構成を提示します。 - エラーバジェット枯渇時の自動対応: バジェットが閾値を下回った際に SNS → Lambda →
Slack/PagerDuty 通知を自動化するパイプラインを Terraform で実装する手順を含みます。
前提シリーズ: AWS 本番運用シリーズ 9 巻 (既読推奨)
| シリーズ | Vol1 | Vol2 | Vol3 |
|---|---|---|---|
| Lambda 本番運用 | Container Image デプロイ | SnapStart 応用 | Powertools+Layers |
| EKS 本番運用 | Cluster+Karpenter | IRSA | ALB+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 巻シリーズの設計思想です。