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 Type | OTEL Attribute |
|---|---|
string | String |
int, int32, int64 | Int64 |
uint, uint32, uint64 | Int64 |
float32, float64 | Float64 |
bool | Bool |
[]byte | Bytes |
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)