OpenTelemetryはClueにおける可観測性の主要なソースですが、ログは特定のシナリオで 重要な役割を果たします。Clueは、ログメッセージを効率的にバッファリングし フォーマットするスマートなロギングシステムを提供し、コストとパフォーマンスを 制御しながら可視性を維持するのに役立ちます。
スマートバッファリング: Clueのスマートバッファリングシステムは、ロギングのコストとパフォーマンスの 最適化に役立ちます。エラー以外のログをメモリにバッファリングし、エラーが 発生した時にバッファをフラッシュしてエラーの周辺の完全なコンテキストを提供します。 トレースされたリクエストの場合、リクエストのライフサイクルの完全な可視性を 確保するためにログは自動的にフラッシュされます。また、特定のシナリオでログ出力を 強制する必要がある場合に、手動でフラッシュを制御することもできます。柔軟性を 維持するために、バッファリングの動作はコンテキストに基づいて設定でき、 異なる状況に応じてロギングパターンを適応させることができます。
構造化ロギング: Clueは、ログをより有用で保守しやすくするために構造化ロギングを使用します。 すべてのログフィールドはキーと値のペアとして保存され、ロギングツールで簡単に 解析および分析できることを保証します。すべてのログで一貫したフォーマットを 使用することで、監視および分析システムとの統合が改善されます。ログは環境の ニーズに応じてJSONやプレーンテキストなど異なる形式で出力できます。 さらに、コンテキストベースのフィールドが自動的に含まれ、ログをリクエストの トレースと相関付けることで、分散システム全体での問題のデバッグが容易になります。
パフォーマンス: Clueのロギングシステムはパフォーマンスを考慮して設計されています。 I/Oオーバーヘッドを最小限に抑えるために効率的なバッファリング技術を使用し、 アロケーションオーバーヘッドを低く抑えるためにスマートなメモリ管理を採用しています。 システムは環境のニーズに基づいて異なる方法でログを出力するように設定できます。 また、条件付きロギングを通じてログ量を制御し、必要なログのみを生成することを 確保できます。
ロガーは使用前に設定する必要があります。以下は一般的なオプションでロガーを セットアップする方法です:
// オプション付きでロガーコンテキストを作成
ctx := log.Context(context.Background(),
// トレースとの相関のためにログにスパンIDを含める
log.WithFunc(log.Span),
// 機械可読性のためにJSONフォーマットを使用
log.WithFormat(log.FormatJSON),
// 標準出力にログを送信
log.WithOutput(os.Stdout),
// リクエストがトレースされている場合はバッファリングを無効化
log.WithDisableBuffering(log.IsTracing))
// すべてのログに含まれる共通フィールドを追加
ctx = log.With(ctx,
log.KV{"service", "myservice"}, // フィルタリング用のサービス名
log.KV{"env", "production"}) // コンテキスト用の環境
この設定により、サービスの堅牢なロギング基盤が確立されます。ログにスパンIDを 含めることで、ログエントリを分散トレースと簡単に相関付けることができ、システム 全体でのリクエストフローの完全な全体像を把握できます。JSONフォーマットにより、 ログ集約および分析ツールでログを効率的に処理できることが保証されます。
ローカル開発の利便性のため、ログは端末で簡単に表示できるように標準出力に 出力されます。スマートバッファリングシステムは、リクエストがトレースされているかどうかに 基づいて自動的に調整され、パフォーマンスと可観測性の両方を最適化します。
最後に、セットアップには、すべてのログエントリに追加される共通フィールドが含まれ、 フィルタリングと分析のための一貫したコンテキストを提供します。サービス名や環境などの これらのフィールドにより、問題を調査する際に各ログエントリのソースとコンテキストを 簡単に特定できます。
Clueは4つの重要度レベルをサポートし、それぞれが特定の目的を持ち、異なるバッファリング ルールに従います:
// デバッグレベル - 詳細なトラブルシューティング用
// WithDebugを介してデバッグモードが有効な場合のみ出力
log.Debug(ctx, "リクエストの詳細",
log.KV{"headers", req.Headers},
log.KV{"body_size", len(req.Body)})
// 情報レベル - 通常の操作用
// これらのログはデフォルトでバッファリングされ、エラー時にフラッシュ
log.Info(ctx, "リクエストを処理中",
log.KV{"requestID", req.ID},
log.KV{"method", req.Method})
// 警告レベル - 潜在的な問題用
// これらのログは操作を妨げない問題を示す
log.Warn(ctx, "リソース使用量が高い",
log.KV{"cpu_usage", cpuUsage},
log.KV{"memory_usage", memUsage})
// エラーレベル - 失敗条件用
// これらのログは即座に書き込まれ、バッファをフラッシュ
log.Error(ctx, err, "リクエストが失敗",
log.KV{"requestID", req.ID},
log.KV{"status", http.StatusInternalServerError})
// 致命的レベル - 回復不可能なエラー用
// これらのログはログ記録後にプログラムを終了
log.Fatal(ctx, err, "サーバーを起動できません",
log.KV{"port", config.Port},
log.KV{"error", err.Error()})
各レベルにはprintf形式のフォーマットを受け入れる対応するバージョンもあります:
// フォーマット付きデバッグ
log.Debugf(ctx, "アイテム %d / %d を処理中", current, total)
// フォーマット付き情報
log.Infof(ctx, "リクエストが %dms で完了", duration.Milliseconds())
// フォーマット付き警告
log.Warnf(ctx, "高レイテンシーを検出: %dms", latency.Milliseconds())
// エラーオブジェクトとフォーマット付きエラー
log.Errorf(ctx, err, "リクエストの処理に失敗: %s", req.ID)
// エラーオブジェクトとフォーマット付き致命的エラー
log.Fatalf(ctx, err, "初期化に失敗: %s", component)
ログレベルのベストプラクティス:
DEBUG (SeverityDebug):
INFO (SeverityInfo):
WARN (SeverityWarn):
ERROR (SeverityError):
FATAL (SeverityError + Exit):
特別な動作:
カラーコード(端末フォーマット使用時):
構造化ロギングにより、ログの解析と分析が容易になります。以下は異なる方法で ログを構造化する方法です:
// 順序付きキーと値のペアにlog.KVを使用
// フィールドの順序を維持するため、これが推奨される方法
log.Print(ctx,
log.KV{"action", "user_login"}, // 何が起きたか
log.KV{"user_id", user.ID}, // 誰に起きたか
log.KV{"ip", req.RemoteAddr}, // 追加のコンテキスト
log.KV{"duration_ms", duration.Milliseconds()}) // パフォーマンスデータ
// マップスタイルのロギングにlog.Fieldsを使用
// 既存のデータマップを扱う場合に有用
log.Print(ctx, log.Fields{
"action": "user_login",
"user_id": user.ID,
"ip": req.RemoteAddr,
"duration_ms": duration.Milliseconds(),
})
// 以降のすべてのログに含まれるコンテキストフィールドを追加
// リクエストスコープの情報に有用
ctx = log.With(ctx,
log.KV{"tenant", tenant.ID}, // マルチテナントコンテキスト
log.KV{"region", "us-west"}, // 地理的コンテキスト
log.KV{"request_id", reqID}) // リクエストの相関
構造化ロギングのベストプラクティス:
環境とツールに合わせて出力フォーマットを選択:
// プレーンテキストフォーマット(logfmt)
// ローカル開発と人間の可読性に最適
ctx := log.Context(context.Background(),
log.WithFormat(log.FormatText))
// 出力: time=2024-02-24T12:34:56Z level=info msg="hello world"
// カラー付き端末フォーマット
// ローカル開発とデバッグに最適
ctx := log.Context(context.Background(),
log.WithFormat(log.FormatTerminal))
// 出力: INFO[0000] msg="hello world"
// JSONフォーマット
// 本番環境とログ集約システムに最適
ctx := log.Context(context.Background(),
log.WithFormat(log.FormatJSON))
// 出力: {"time":"2024-02-24T12:34:56Z","level":"info","msg":"hello world"}
// カスタムフォーマット
// 特別なフォーマットが必要な場合に使用
ctx := log.Context(context.Background(),
log.WithFormat(func(entry *log.Entry) []byte {
return []byte(fmt.Sprintf("[%s] %s: %s\n",
entry.Time.Format(time.RFC3339),
entry.Level,
entry.Message))
}))
// 出力: [2024-02-24T12:34:56Z] info: hello world