CloudWatch Logs のエラーを自動で GitHub Issues に課題化する
ECS で稼働するWebアプリケーションのエラーログを自動的に GitHub Issues に報告する仕組みを構築しました。手動でログを監視する必要がなくなり、エラー発生時に即座にチームが認識・対応できるようになります。
背景
マルチテナントの業務システムを ECS Fargate 上で運用しています。アプリケーションは2つあり、それぞれ異なるフレームワークで構築されています。
| アプリ | フレームワーク | 用途 |
|---|---|---|
| web | Laravel (PHP) | 業務管理システム |
| api | Django (Python) | API サーバー |
これまで CloudWatch Logs にログは収集していたものの、エラーの検知は手動確認に頼っていました。500エラーや例外発生を見逃すリスクがあり、自動検知の仕組みが必要でした。
アーキテクチャ
Subscription Filter + Lambda + GitHub Issues API の構成を採用しました。
CloudWatch Logs (/ecs/{prefix}-ecs-{app})
└── Subscription Filter (エラーパターンマッチ)
└── Lambda Function (Docker/arm64, Python 3.12)
├── エラー解析 (HTTP 5xx, 例外, スタックトレース)
├── ±5秒のログコンテキスト取得
├── 既存 Open Issue 検索
└── 新規 Issue 作成 or 既存 Issue にコメント追加
この構成を選んだ理由
| 方式 | リアルタイム性 | 柔軟性 | コスト |
|---|---|---|---|
| Subscription Filter + Lambda (採用) | 高 | 高 | 中 |
| Metric Filter + Alarm + SNS | 中 (1分以上遅延) | 低 | 低 |
| CloudWatch Logs Insights (定期実行) | 低 | 高 | 低 |
Subscription Filter はログ出力時にほぼリアルタイムで Lambda を起動するため、エラー発生から数秒で Issue が作成されます。
実装のポイント
1. アプリごとにリポジトリを分離
エラーの報告先を Secrets Manager で管理し、アプリ名に応じてリポジトリを切り替えます。
| |
Lambda ハンドラーでは、ログストリーム名からアプリ名を抽出してリポジトリを決定します。
| |
2. PHP と Python の両方のエラーパターンに対応
Laravel (PHP) と Django (Python) でエラーの出力形式が異なるため、両方のパターンを検出します。
Subscription Filter のフィルタパターン (Laravel):
?"HTTP/1.1\" 5" ?"ERROR" ?"Traceback" ?"CRITICAL" ?"PHP Fatal" ?"production.ERROR"
Python コードでのパターンマッチ:
| |
3. スタックトレースのコンテキスト取得
Subscription Filter はフィルタに一致した行のみを Lambda に送信します。しかし、Python の Traceback や PHP のスタックトレースは複数行にまたがるため、エラーの全容がわかりません。
そこで、CloudWatch Logs API で エラー発生時刻の前後±5秒のログを取得し、連続するエラー行を1つのブロックにグルーピングします。
| |
4. 連続投稿の抑制 (クールダウン)
エラーが大量発生すると Issue へのコメントが洪水のように投稿されてしまいます。これを防ぐため、10分間のクールダウンを実装しています。
| |
5. Docker イメージベースの Lambda
エラー解析ロジックが複雑化することを見越して、Docker イメージベースの Lambda を採用しました。
- ローカルで
pytestによるユニットテスト実行が可能 - 依存パッケージの管理が容易
- ビルドスクリプトがテスト → ビルド → プッシュを自動化
| |
Terraform モジュールの構成
modules/log_monitor/ として再利用可能なモジュールにまとめています。
modules/log_monitor/
├── main.tf # Subscription Filter
├── lambda.tf # Lambda 関数 (Docker Image/arm64)
├── iam.tf # IAM ロール・ポリシー
├── secrets.tf # Secrets Manager
├── repository.tf # ECR リポジトリ
├── logs.tf # Lambda 用 Log Group
├── locals.tf # フィルタ定義
├── variables.tf / outputs.tf
├── bin/build.bash # ビルドスクリプト
└── docker/ # Lambda ソースコード + テスト
環境ごとの設定は JSON ファイルで外出しします。
| |
GitHub Issue の出力例
Lambda が自動作成する Issue は以下のような形式です。
| |
ラベル CloudWatchLog + アプリ名が付与され、同一ラベルの Open Issue が既にあればコメントとして追記されます。
デプロイ手順
3ステップでデプロイできます。
| |
Sentry との比較
エラー監視の SaaS として広く使われている Sentry と、本構成(CloudWatch + Lambda + GitHub Issues)を比較します。
本構成の強み
| 強み | 詳細 |
|---|---|
| GitHub Issues との直接統合 | エラーがそのまま開発チームのワークフロー(Issue → PR → Close)に乗る |
| AWS に閉じている | 外部 SaaS への依存がなく、エラー情報が AWS 外に出ない |
| コスト透明性 | Lambda 実行回数ベースで、低〜中トラフィック環境ならほぼ無料 |
| カスタマイズ自由 | フィルタパターン、クールダウン時間、コンテキスト取得範囲を自由に調整可能 |
Sentry が優れている領域
一方で、Sentry には本構成で再現が難しい、または自作すると大きなコストがかかる機能があります。
1. エラーのグルーピングとデデュプリケーション
本構成では「同一ラベルの Open Issue があればコメント追記 + 10分クールダウン」で重複を抑制していますが、異なる原因のエラーが1つの Issue に混在する可能性があります。Sentry はスタックトレースの構造を解析し、同じ原因のエラーを自動的に1つの Issue にまとめます。発生回数・影響ユーザー数のカウントも自動です。
2. ソースコードレベルのコンテキスト
本構成では ±5秒のログからエラーブロックを構成しますが、Sentry はスタックトレースの各フレームに対して該当するソースコードの前後数行を表示します。リリースバージョンと紐づけて「どのデプロイで混入したか」も追跡可能です。
3. パフォーマンスモニタリング(トレーシング)
本構成はエラー(5xx、例外)のみを検知しますが、Sentry Performance はリクエスト全体のトレースを記録し、遅いクエリ、遅い外部 API 呼び出し、N+1 クエリを自動検出します。エラーにならないが遅い処理(P95 レイテンシの劣化等)も可視化できます。これを自前で実装するには OpenTelemetry + Jaeger/Tempo 等の分散トレーシング基盤が必要になり、構築コストが大きくなります。
4. リリースとの紐づけ
「このエラーはリリース v1.2.3 で初めて発生した」「このリリースで regression が起きている」をダッシュボードで確認でき、コミットとの紐づけにより「誰のどのコミットが原因か」まで追跡可能です。本構成ではログの timestamp から推測するしかありません。
5. ユーザーコンテキスト
Sentry SDK はエラー発生時に「どのユーザーが」「どのブラウザ/OS で」「どのページで」操作していたかを自動記録します。マルチテナント環境では「どのテナントに影響しているか」の把握が重要ですが、ログのパースだけでは限界があります。
6. アラートの柔軟性
「過去1時間で同じエラーが50回以上発生したら Slack に通知」「新規エラーのみ通知」「特定のエラーは無視」といったルールを GUI で設定可能です。本構成では通知の粒度を変えるには Lambda コードの修正が必要です。
Sentry のデメリット
| 観点 | 詳細 |
|---|---|
| コスト | Team プランは月 $26〜。エラー量が増えるとイベント数課金が嵩む |
| データの外部送出 | エラー情報(スタックトレース、リクエストデータ)が Sentry のサーバーに送信される。セキュリティポリシーによっては不可(セルフホスト版もあるが運用負荷が大きい) |
| 監視ツールの分散 | CloudWatch と Sentry の両方を見る必要があり、チームが確認する場所が増える |
使い分けの指針
両者を置き換える関係ではなく、役割を分けるのが現実的です。
| 役割 | ツール |
|---|---|
| インフラ・ログレベルの異常検知、Issue 化 | 本構成(CloudWatch + Lambda + GitHub Issues) |
| アプリケーションレベルのエラー分析・パフォーマンス | Sentry(導入する場合) |
Sentry の導入が特に効果的になるのは以下のような状況です。
- エラーの種類が多すぎて GitHub Issues では追いきれなくなった
- 「遅いがエラーにはならない」パフォーマンス問題を追いたい
- リリース頻度が高く、どのデプロイで regression が起きたかを素早く特定したい
- 影響ユーザー数をテナント単位で把握したい
MVP フェーズやチーム規模が小さいうちは、本構成だけで十分にカバーできます。
まとめ
- Subscription Filter + Lambda でセミリアルタイムのエラー検知を実現
- アプリごとにリポジトリを分離し、開発チームが自然に Issue を確認できる
- PHP/Laravel と Python/Django の両方のエラーパターンに対応
- スタックトレースのコンテキスト取得でエラーの全容を報告
- 10分クールダウンで Issue の洪水を防止
- Docker Lambda + pytest でテスト容易性を確保
- Terraform モジュール化で複数環境への展開が容易