コンテキスト統合
DD は Go 標準ライブラリ context.Context との統合をサポートし、トレーシング情報の自動伝播とコンテキストフィールドの抽出が可能です。
ContextKey 型
ContextKey は string をベースとしたカスタムキー型で、他パッケージのコンテキストキーとの競合を回避します。
type ContextKey stringTraceID / SpanID / RequestID に対応する 3 つのキー定数が定義済みです:
| 定数 | 型 | 値 |
|---|---|---|
ContextKeyTraceID | ContextKey | "trace_id" |
ContextKeySpanID | ContextKey | "span_id" |
ContextKeyRequestID | ContextKey | "request_id" |
注入と読み取り
| 関数 | シグネチャ | 説明 |
|---|---|---|
WithTraceID | (ctx context.Context, traceID string) context.Context | TraceID を注入 |
WithSpanID | (ctx context.Context, spanID string) context.Context | SpanID を注入 |
WithRequestID | (ctx context.Context, requestID string) context.Context | RequestID を注入 |
GetTraceID | (ctx context.Context) string | TraceID を読み取り(欠損時は "" を返す) |
GetSpanID | (ctx context.Context) string | SpanID を読み取り(欠損時は "" を返す) |
GetRequestID | (ctx context.Context) string | RequestID を読み取り(欠損時は "" を返す) |
With* 関数は context.WithValue に基づいて新しい ctx を派生し(キーは対応する ContextKey 定数)、Get* 関数は ctx から string 値を取り出します;キーが存在しない、または値が string でない場合は、一律に空文字列を返します。
使用例
func handleRequest(ctx context.Context) {
// トレーシング情報を注入
ctx = dd.WithTraceID(ctx, "trace-abc123")
ctx = dd.WithSpanID(ctx, "span-def456")
ctx = dd.WithRequestID(ctx, "req-789")
// コンテキストフィールドを手動抽出してログに渡す
logger.InfoWith("リクエスト処理",
dd.String("trace_id", dd.GetTraceID(ctx)),
dd.String("span_id", dd.GetSpanID(ctx)),
dd.String("request_id", dd.GetRequestID(ctx)),
)
}一括抽出
手動 Get* は単発の場面に適しています。グローバル/静的フィールド(サービス名やホスト名など)を毎ログに自動付与するには、ContextExtractor を Logger に登録します。抽出器は毎回 *With 呼び出し時に実行されます。注意:抽出器が受け取るのは context.Background() であり、リクエストスコープの TraceID を自動取得できません(下記の制限事項を参照)。
ContextExtractor
ContextExtractor は context.Context からフィールドを自動抽出する関数型で、OpenTelemetry、Jaeger などのトレーシングフレームワークとの連携に便利です。
type ContextExtractor func(ctx context.Context) []Fieldエクストラクタは Logger 内部が保持するスレッドセーフなレジストリ(contextExtractorRegistry、プライベートで外部非公開)で管理されます:追加順に実行され、読み取りは atomic.Pointer によるロックフリー高速パスを通ります;いずれかのエクストラクタが panic した場合は recover されて stderr に記録され、アプリケーションをクラッシュさせることはありません。
エクストラクタの登録
エクストラクタ自体はこのファイルで型のみ定義されます;登録/管理 API は Logger 上にあります(core ドメイン):
// エクストラクタを追加(error を返す、nil エクストラクタは拒否されます)
err := logger.AddContextExtractor(func(ctx context.Context) []dd.Field {
return []dd.Field{
dd.String("trace_id", dd.GetTraceID(ctx)),
dd.String("request_id", dd.GetRequestID(ctx)),
}
})
// 全エクストラクタを一括置き換え
_ = logger.SetContextExtractors(extractor1, extractor2)
// 現在登録済みのエクストラクタのスナップショットを取得
extractors := logger.GetContextExtractors()コンテキストの制限(重要)
ログメソッド(Info/InfoWith 等)は context.Context を受け取らず、ContextExtractor は内部で context.Background() で呼ばれるため、リクエストスコープの TraceID/SpanID を自動抽出できません。下記の OTel 例はグローバル span が存在する場合のみフィールドを出力します。リクエストごとのトレース ID を付与するには WithFields() で手動渡してください(分散トレーシング統合 を参照)。
OpenTelemetry 例
// OTel span の trace_id / span_id を各ログに注入
otelExtractor := dd.ContextExtractor(func(ctx context.Context) []dd.Field {
span := trace.SpanFromContext(ctx)
if !span.SpanContext().IsValid() {
return nil
}
return []dd.Field{
dd.String("trace_id", span.SpanContext().TraceID().String()),
dd.String("span_id", span.SpanContext().SpanID().String()),
}
})
_ = logger.AddContextExtractor(otelExtractor)完全な例
HTTP ミドルウェア
func tracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = generateTraceID()
}
ctx := dd.WithTraceID(r.Context(), traceID)
ctx = dd.WithRequestID(ctx, generateRequestID())
next.ServeHTTP(w, r.WithContext(ctx))
})
}gRPC インターセプター
func loggingInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
md, _ := metadata.FromIncomingContext(ctx)
ctx = dd.WithTraceID(ctx, md.Get("trace-id")[0])
ctx = dd.WithRequestID(ctx, md.Get("request-id")[0])
dd.InfoWith("gRPC リクエスト",
dd.String("method", info.FullMethod),
dd.String("trace_id", dd.GetTraceID(ctx)),
dd.String("request_id", dd.GetRequestID(ctx)),
)
return handler(ctx, req)
}