ストーリー
高橋アーキテクトが同意した。
ログ集約のアーキテクチャ
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Service A │ │ Service B │ │ Service C │
│ (logs) │ │ (logs) │ │ (logs) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ Log Shipper / Agent │
│ (Fluentd, Fluent Bit, Filebeat) │
└────────────────────┬────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Log Storage & Index │
│ (Elasticsearch, CloudWatch, Loki) │
└────────────────────┬────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Search & Visualization │
│ (Kibana, CloudWatch Insights, │
│ Grafana) │
└─────────────────────────────────────────┘
ELK Stack(Elasticsearch + Logstash + Kibana)
アーキテクチャ
// ELK Stackの構成
interface ELKStack {
elasticsearch: {
role: 'ログの保存とインデックス作成';
features: ['全文検索', '集計', 'フィルタリング'];
};
logstash: {
role: 'ログの収集・変換・送信';
features: ['パイプライン処理', 'フィルタ', 'エンリッチメント'];
};
kibana: {
role: 'ログの可視化と検索UI';
features: ['ダッシュボード', 'クエリ', 'アラート'];
};
}
Logstashパイプラインの例
# logstash.conf
input {
beats {
port => 5044
}
}
filter {
json {
source => "message"
}
# traceIdが存在する場合にインデックスを分ける
if [level] == "ERROR" {
mutate {
add_tag => ["error"]
}
}
# タイムスタンプのパース
date {
match => ["timestamp", "ISO8601"]
target => "@timestamp"
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
CloudWatch Logs(AWS)
// CloudWatch Logs Insightsのクエリ例
const queries = {
// 過去1時間のエラーログ
recentErrors: `
fields @timestamp, @message, orderId, errorCode
| filter level = "ERROR"
| sort @timestamp desc
| limit 100
`,
// サービス別エラー数の集計
errorsByService: `
fields service
| filter level = "ERROR"
| stats count(*) as errorCount by service
| sort errorCount desc
`,
// 特定traceIdの全ログを時系列で取得
traceSearch: `
fields @timestamp, service, level, message
| filter traceId = "abc123def456"
| sort @timestamp asc
`,
// レスポンスタイムのパーセンタイル分析
latencyAnalysis: `
fields duration
| filter path = "/api/orders"
| stats avg(duration) as avg_ms,
pct(duration, 50) as p50,
pct(duration, 95) as p95,
pct(duration, 99) as p99
`,
};
Grafana Loki
// Loki: Prometheusライクなラベルベースのログ管理
interface LokiConfig {
advantages: [
'Prometheusと同じラベルモデルで親和性が高い',
'ログの中身をインデックスしないため、ストレージコストが低い',
'Grafanaとネイティブに統合',
];
tradeoff: 'フルテキスト検索はElasticsearchほど高速ではない';
}
// LogQLクエリの例
const logqlQueries = {
// サービスのエラーログ
serviceErrors: '{service="payment-service"} |= "ERROR"',
// JSON解析してフィルタ
parsedFilter: '{service="api-gateway"} | json | status >= 500',
// 5分間のエラー率
errorRate: 'sum(rate({service="api-gateway"} | json | level="ERROR" [5m]))',
};
ツール比較
| 項目 | ELK Stack | CloudWatch Logs | Grafana Loki |
|---|---|---|---|
| デプロイ | セルフホスト/マネージド | フルマネージド(AWS) | セルフホスト/クラウド |
| 検索方式 | フルテキスト検索 | クエリ言語 | ラベル + フィルタ |
| コスト | ストレージ + 運用 | 取り込み量 + クエリ量 | ストレージのみ(安い) |
| スケーラビリティ | 高い(運用コスト大) | 自動スケール | 高い(S3バックエンド) |
| 適したケース | 大規模検索・分析 | AWSネイティブ環境 | Prometheus環境 |
まとめ
| ポイント | 内容 |
|---|---|
| 集約の原則 | 分散したログを1箇所に集めて横断検索 |
| ELK Stack | フルテキスト検索に強い、大規模分析向き |
| CloudWatch | AWS環境でのフルマネージドソリューション |
| Grafana Loki | 低コストでPrometheus環境と親和性が高い |
チェックリスト
- ログ集約のアーキテクチャパターンを説明できる
- ELK Stackの3つのコンポーネントの役割を理解した
- CloudWatch Logs Insightsのクエリを書ける
- 3つのツールの特徴と使い分けを把握した
次のステップへ
次は「ログによるデバッグとトラブルシューティング」を学びます。集約したログを使って、実際に障害を調査する方法を見ていきましょう。
推定読了時間: 30分