zoobzio December 12, 2025 Edit this page

Context Extraction Guide

Extract values from context.Context and add them as attributes to logs, metrics, and traces.

Registration

Context keys must be registered before use:

type ctxKey string
const (
    userIDKey    ctxKey = "user_id"
    regionKey    ctxKey = "region"
    requestIDKey ctxKey = "request_id"
)

ap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)

// Register context keys by name
ap.RegisterContextKey("user_id", userIDKey)
ap.RegisterContextKey("region", regionKey)
ap.RegisterContextKey("request_id", requestIDKey)

Configuration

Specify which context keys to extract for each signal type:

schema := aperture.Schema{
    Context: &aperture.ContextSchema{
        Logs: []string{"user_id", "request_id"},
        Metrics: []string{"region"},  // Low cardinality only
        Traces: []string{"user_id", "region"},
    },
}
ap.Apply(schema)

Usage

Add values to context, then emit events:

ctx := context.Background()
ctx = context.WithValue(ctx, userIDKey, "user-123")
ctx = context.WithValue(ctx, regionKey, "us-east-1")
ctx = context.WithValue(ctx, requestIDKey, "req-456")

cap.Emit(ctx, orderCreated, orderID.Field("ORD-789"))

Results:

  • Log: user_id="user-123", request_id="req-456", order_id="ORD-789"
  • Metric: region="us-east-1" dimension
  • Trace span: user_id="user-123", region="us-east-1" attributes

Supported Value Types

Context values are converted to attributes:

Go TypeOTEL Attribute
stringString
int, int32, int64Int64
uint, uint32, uint64Int64
float32, float64Float64
boolBool
[]byteBytes

Other types are skipped silently.

Missing Values

If a context key is configured but not present:

  • The attribute is simply not added
  • No error or warning
  • Other attributes still extracted
schema := aperture.Schema{
    Context: &aperture.ContextSchema{
        Logs: []string{"user_id", "session_id"},
    },
}

// Only user_id set
ctx = context.WithValue(ctx, userIDKey, "user-123")

cap.Emit(ctx, sig)
// Log includes user_id="user-123"
// session_id not present, not added

Cardinality Warning for Metrics

Metric dimensions multiply storage:

// GOOD for metrics: bounded, low cardinality
regionKey    // ~10-20 values
tierKey      // ~3 values
environmentKey // ~3 values

// BAD for metrics: unbounded, high cardinality
userIDKey    // ~millions of values
requestIDKey // ~infinite values
sessionKey   // ~millions of values

Separate extraction configs let you be selective:

schema := aperture.Schema{
    Context: &aperture.ContextSchema{
        // High cardinality OK for logs and traces
        Logs: []string{"user_id", "request_id"},
        Traces: []string{"user_id", "request_id"},
        // Only low cardinality for metrics
        Metrics: []string{"region", "tier"},
    },
}

Middleware Pattern

Common pattern: middleware adds context values that propagate through request handling:

func RequestContextMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()

        // Add request ID
        requestID := r.Header.Get("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }
        ctx = context.WithValue(ctx, requestIDKey, requestID)

        // Add user from auth
        if userID := getUserFromAuth(r); userID != "" {
            ctx = context.WithValue(ctx, userIDKey, userID)
        }

        // Add region from header or default
        region := r.Header.Get("X-Region")
        if region == "" {
            region = "us-east-1"
        }
        ctx = context.WithValue(ctx, regionKey, region)

        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

All events emitted during request handling automatically include these values.

Combining with Event Fields

Context extraction complements event fields:

// Context: request-scoped values
ctx = context.WithValue(ctx, userIDKey, "user-123")
ctx = context.WithValue(ctx, regionKey, "us-east-1")

// Fields: event-specific values
cap.Emit(ctx, orderCreated,
    orderID.Field("ORD-456"),
    total.Field(99.99),
)

// Log includes all:
// user_id="user-123" (from context)
// region="us-east-1" (from context)
// order_id="ORD-456" (from field)
// total=99.99 (from field)

Schema Configuration

Via YAML:

context:
  logs:
    - user_id
    - request_id
  metrics:
    - region
    - tier
  traces:
    - user_id
    - region

Register context keys before applying:

ap.RegisterContextKey("user_id", userIDKey)
ap.RegisterContextKey("request_id", requestIDKey)
ap.RegisterContextKey("region", regionKey)
ap.RegisterContextKey("tier", tierKey)

configBytes, _ := os.ReadFile("config.yaml")
schema, _ := aperture.LoadSchemaFromYAML(configBytes)
ap.Apply(schema)