ログ設計・ログ方針
Bazbiiシステムのログ設計と運用方針を説明します。
ログ設計の概要
ログアーキテクチャ
アプリケーション
↓
slog (Go標準ロガー)
↓
┌─────────┬──────────────┐
│ stdout │ OpenTelemetry │
└─────────┴──────────────┘
↓ ↓
Loki OTel Collector
ログの出力先
- stdout: 開発環境、デバッグ用
- OpenTelemetry: 本番環境、ログ集約システムへ送信
ログレベル
レベルと使い分け
- Debug: 詳細なデバッグ情報(開発環境のみ)
- Info: 正常系の重要な処理(リクエスト完了、イベント発生)
- Warn: 警告(回復可能なエラー、非推奨機能の使用)
- Error: エラー(処理失敗、例外)
レベル選択のガイドライン
// ✅ 正しい使い方
logger.Debug("processing request", "request_id", reqID) // 詳細な処理ログ
logger.Info("request completed", "duration_ms", duration) // 正常完了
logger.Warn("rate limit approaching", "remaining", remaining) // 警告
logger.Error("database connection failed", "error", err) // エラー
// ❌ 避けるべき使い方
logger.Info("error occurred", "error", err) // エラーはErrorレベル
logger.Error("request received") // 正常系はInfoレベル
構造化ログ
ログフォーマット
Bazbiiでは構造化ログ(JSON形式)を使用します。
// 構造化ログの例
logger.Info("post created",
slog.String("log_id", "EVT_POST_001"),
slog.String("post_id", postID.String()),
slog.String("actor_id", actorID.String()),
slog.String("h3_index", h3Index.String()),
slog.Time("created_at", time.Now()),
)
ログメッセージの管理
ログメッセージはserver-platform/observability/logmsg/で一元管理します。
// logmsg/events.go
var domainEventMapping = map[string]LogMsg{
"post.created": {
ID: "EVT_POST_001",
Template: "post created",
Level: slog.LevelInfo,
},
"post.deletion_failed": {
ID: "EVT_POST_003",
Template: "post deletion failed",
Level: slog.LevelError,
},
}
ログID体系
ID形式
[種類]_[カテゴリ]_[連番]
種類
- REQ_: リクエストライフサイクル(REQ_001, REQ_002, ...)
- EVT_: ドメインイベント(EVT_POST_001, EVT_AUTH_001, ...)
- API_: API呼び出し(API_CALL_001, API_ERROR_001, ...)
- DB_: データベース操作(DB_QUERY_001, DB_ERROR_001, ...)
- OBS_: オブザーバビリティ初期化(OBS_INIT_001, OBS_FAILED_001, ...)
カテゴリ
- POST: 投稿関連
- AUTH: 認証関連
- FEED: フィード関連
- HEATMAP: ヒートマップ関連
例
EVT_POST_001 → Post created
EVT_POST_002 → Post deleted
EVT_POST_003 → Post creation failed
REQ_001 → Request received
REQ_012 → API service call failed
ログ出力の実装
Contextベースのロガー
// Contextからロガーを取得
logger := logger.FromContext(ctx)
logger.Info("processing request", "request_id", reqID)
ログメッセージの出力
// ログメッセージマスタを使用
logmsg.Provisioned.Emit(ctx,
slog.String("actor_id", actorID.String()),
slog.String("device_id", deviceID.String()),
)
// エラーを含むログ
logmsg.ProvisioningFailed.EmitError(ctx, err,
slog.String("actor_id", actorID.String()),
)
ドメインイベントのログ化
// イベントパブリッシャーでログに変換
publisher.Publish(ctx, event) // → ログに自動変換
ログに含めるべき情報
必須情報
- log_id: ログメッセージID
- timestamp: タイムスタンプ(自動付与)
- service: サービス名
- trace_id: トレースID(OpenTelemetry連携時)
コンテキスト情報
- request_id: リクエストID(分散トレーシング)
- actor_id: アクターID(認証済みユーザー)
- user_agent: ユーザーエージェント
- ip_address: IPアドレス(必要に応じて)
エラー情報
- error: エラーメッセージ
- error_type: エラーの種類
- stack_trace: スタックトレース(開発環境のみ)
ログに含めない情報
機密情報
- ❌ パスワード、APIキー、トークン
- ❌ 個人情報(メールアドレス、電話番号)※必要最小限に
- ❌ クレジットカード情報
回避方法
// ❌ 避けるべき
logger.Info("user logged in", "password", password)
// ✅ 正しい
logger.Info("user logged in", "actor_id", actorID.String())
ログの集約と監視
開発環境
- 出力先: stdout(JSON形式)
- 確認方法:
make compose/logs
本番環境
- 出力先: OpenTelemetry → Grafana Cloud Loki
- 可視化: Grafana Cloud
- 検索: LogQLクエリ
Grafanaでのログ検索例
# エラーログを検索
{service="gateway"} | json | level="error"
# 特定のログIDで検索
{service="api"} | json | log_id="EVT_POST_003"
# 時間範囲で検索
{service="gateway"} | json | timestamp > now() - 1h
ログのベストプラクティス
1. 適切なログレベルの使用
- デバッグ情報はDebugレベル
- 正常系の重要な処理はInfoレベル
- エラーはErrorレベル
2. 構造化ログの活用
- キー・バリューペアで情報を構造化
- 後で検索・集計しやすい形式
3. ログメッセージIDの使用
- ログメッセージマスタで一元管理
- ログIDで検索・集計が容易
4. コンテキスト情報の付与
- リクエストID、トレースIDを付与
- 分散トレーシングで全体像を把握
関連ドキュメント
- 監視 - ログ監視の設定
- エラーハンドリング - エラーログの扱い
- インフラストラクチャ - OpenTelemetryの設定