Skip to content

Context Integration ​

DD supports integration with the Go standard library's context.Context, enabling automatic propagation of tracing information and extraction of context fields.

ContextKey Type ​

ContextKey is a custom key type based on string, avoiding context-key collisions with other packages.

go
type ContextKey string

Three predefined key constants correspond to TraceID / SpanID / RequestID:

ConstantTypeValue
ContextKeyTraceIDContextKey"trace_id"
ContextKeySpanIDContextKey"span_id"
ContextKeyRequestIDContextKey"request_id"

Injection and Reading ​

FunctionSignatureDescription
WithTraceID(ctx context.Context, traceID string) context.ContextInject TraceID
WithSpanID(ctx context.Context, spanID string) context.ContextInject SpanID
WithRequestID(ctx context.Context, requestID string) context.ContextInject RequestID
GetTraceID(ctx context.Context) stringRead TraceID (returns "" if missing)
GetSpanID(ctx context.Context) stringRead SpanID (returns "" if missing)
GetRequestID(ctx context.Context) stringRead RequestID (returns "" if missing)

The With* functions derive a new ctx based on context.WithValue (with the key being the corresponding ContextKey constant); the Get* functions retrieve the string value from the ctx. If the key does not exist, the value is not a string, or the ctx is nil, they uniformly return an empty string.

Usage Example ​

go
func handleRequest(ctx context.Context) {
    // Inject tracing info
    ctx = dd.WithTraceID(ctx, "trace-abc123")
    ctx = dd.WithSpanID(ctx, "span-def456")
    ctx = dd.WithRequestID(ctx, "req-789")

    // Manually extract context fields and pass them into the log
    logger.InfoWith("processing request",
        dd.String("trace_id", dd.GetTraceID(ctx)),
        dd.String("span_id", dd.GetSpanID(ctx)),
        dd.String("request_id", dd.GetRequestID(ctx)),
    )
}

Batch extraction

Manual Get* is suitable for one-off scenarios. If you need every log line to automatically carry global/static fields (such as service name or hostname), register a ContextExtractor on the Logger as shown below; the extractor runs on every log entry (Info/Error/*With and all other entry points share the same pipeline). Note: the extractor receives context.Background() and cannot automatically obtain request-scoped TraceIDs (see limitation below).

ContextExtractor ​

ContextExtractor is a function type that automatically extracts fields from context.Context, convenient for integrating with tracing frameworks such as OpenTelemetry and Jaeger.

go
type ContextExtractor func(ctx context.Context) []Field

Extractors are held internally by the Logger in a thread-safe registry (contextExtractorRegistry, private, not exposed): executed in insertion order; reads take an atomic.Pointer lock-free fast path; any extractor panic is recovered and logged to stderr without crashing the application.

Registering Extractors ​

The extractor type itself is defined in this file; registration/management APIs live on the Logger (core domain):

go
// Append an extractor; a nil extractor returns ErrNilExtractor, a closed logger returns ErrLoggerClosed
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)),
    }
})

// Replace all extractors in batch; a no-arg call clears them, a closed logger returns ErrLoggerClosed
_ = logger.SetContextExtractors(extractor1, extractor2)

// Read a snapshot of the currently registered extractors
extractors := logger.GetContextExtractors()

Context limitation (important)

Log methods (Info/InfoWith, etc.) do not accept a context.Context parameter; ContextExtractor is invoked internally with context.Background(), so it cannot automatically extract TraceID/SpanID from the request scope. The OTel example below only emits fields when a global span exists; to attach tracing IDs to each request, pass them manually via WithFields() (see Distributed Tracing Integration).

OpenTelemetry Example ​

go
// Inject the trace_id / span_id of an OTel span into every log line
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)

Complete Examples ​

HTTP Middleware ​

go
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 Interceptor ​

go
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 request",
        dd.String("method", info.FullMethod),
        dd.String("trace_id", dd.GetTraceID(ctx)),
        dd.String("request_id", dd.GetRequestID(ctx)),
    )
    return handler(ctx, req)
}

Next Steps ​

  • Logger -- AddContextExtractor / SetContextExtractors / GetContextExtractors
  • Structured Fields -- Field constructors and field validation
  • Config -- Config.ContextExtractors