はじめに
近年のソフトウェア開発では、システムの大規模化やクラウドサービスの普及に伴い、マイクロサービスアーキテクチャの採用が進んでいます。
マイクロサービスは、システムを独立した小さなサービスに分割することで、開発の柔軟性・スケーラビリティ・耐障害性を高めることができます。
しかし一方で、サービス間通信が複雑化するため、**ログの管理とトレーサビリティ(追跡性)**が課題となります。
本記事では、この「ログ管理の煩雑さ」を解消するために、実践的なベストプラクティスを紹介します。
マイクロサービスのメリットとデメリット
メリット
- 独立性と柔軟性: サービスごとに開発・デプロイ可能。影響範囲を限定できる。
- 技術選択の自由: サービスごとに最適な技術スタックを利用可能。
- スケーラビリティ: 必要なサービスだけを水平スケールできる。
- アジリティ: 小規模なチームが独立して機能追加・改善を進めやすい。
デメリット
- リクエストが複数サービスを横断: 単一の処理フローを追うのが困難。
- ログの分散: 各サービスが独立してログを出力し、全体像を掴みにくい。
- 監視の複雑化: サービスが増えるほど、モニタリングや通知設計も複雑に。
実践
想定構成
例として、複数のAWS Lambda関数が連携し、最終的にデータベースや外部SaaSにアクセスする構成を考えます。
この場合、各Lambdaは独立してログを出力するため、単純にCloudWatchを見るだけではリクエスト全体の流れを追いにくくなります。
ログ出力(Python)
基本的な利用方法
標準ライブラリ logging を利用すれば簡単にログを出力可能です。
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
これは小規模なサービスであれば十分ですが、マイクロサービス全体で追跡するには不十分です。
応用編: カスタムロガー & 構造化ログ
複数サービスを横断するリクエストを追跡するには、構造化ログ(JSON形式)と共通のリクエストIDが不可欠です。
import logging, json, os
class JsonFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record, self.datefmt),
"level": record.levelname,
"service": os.getenv("SERVICE_NAME", "unknown"),
"request_id": getattr(record, "request_id", "N/A"),
"message": record.getMessage(),
"details": getattr(record, "details", {})
}
return json.dumps(log_entry, ensure_ascii=False)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter('%Y-%m-%dT%H:%M:%S%z'))
logger.addHandler(handler)
ポイント:
-
extra引数を活用し、request_idやdetailsを動的に付与する。 - JSON形式で出力することで、後段の分析や検索が容易になる。
ログ収集と通知
ログを出力するだけでは不十分で、収集・正規化・通知まで含めて設計する必要があります。
- CloudWatch Logs にLambdaごとのログを集約。
- Subscription Filter で特定のログを抽出し、別のLambdaへ転送。
- 正規化とS3集約: JSONフォーマットで整理し、長期保管やAthenaによる分析に備える。
-
通知連携:
- CloudWatch Alarm → SNS → Slack / PagerDuty
- 例: エラー件数が一定閾値を超えたら即通知
👉 こうした流れを取ると、リアルタイム検知 + 長期分析が両立できます。
分析の実践(CloudWatch Logs Insights)
CloudWatch Logs Insightsを活用すれば、クエリベースで柔軟にログを分析可能です。
サンプルクエリ
サービス別エラー件数
fields @timestamp, service, level
| filter level = "ERROR"
| stats count(*) as error_count by service
| sort error_count desc
リクエストIDでフローを追跡
fields @timestamp, service, message
| filter request_id = "test-request-id-123"
| sort @timestamp asc
特定エラータイプを調査
fields @timestamp, service, details.error_type
| filter level = "ERROR" and details.error_type = "TimeoutError"
| sort @timestamp desc
補足: 実務でさらに有効なポイント
- OpenTelemetry導入: 単なるログに加え、分散トレーシング(Trace ID, Span ID)を仕込むと、リクエスト全体の可視化が格段に楽になる。
- ELK / OpenSearch 連携: CloudWatchだけでなく、ElasticsearchやOpenSearchにログを転送してダッシュボード可視化。
- 構造化ログの共通ライブラリ化: チーム内で統一されたフォーマットを持たせると、全サービス横断で解析可能に。
- PIIやセキュリティ配慮: ユーザー情報や機密データをそのままログに出さないルールを設けることも重要。
まとめ
マイクロサービスでは、ログが分散するため従来以上に統合的なログ戦略が必要です。
- 構造化ログ(JSON形式)で統一
- 共通のリクエストIDを付与
- CloudWatchやS3で収集・正規化
- Insightsや外部ツールで分析
- 通知とトレーシングを組み合わせる
これらを組み合わせることで、効率的なログ管理・迅速なトラブルシューティング・システム全体の可観測性向上を実現できます。