CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-opentelemetry--opentelemetry-context

OpenTelemetry Context propagation mechanism for carrying scoped values across API boundaries and between threads in Java applications

Pending
Overview
Eval results
Files

implicit-context-values.mddocs/

Implicit Context Values

Implicit context values allow objects to store themselves in context without exposing explicit context keys. This pattern is useful for objects that need to be context-aware but want to hide their internal storage mechanism from users.

ImplicitContextKeyed Interface

Objects implementing this interface can be stored in context and automatically manage their own context keys.

interface ImplicitContextKeyed {
    Scope makeCurrent();
    Context storeInContext(Context context);
}

Interface Methods

Make Current

Adds this value to the current context and makes the new context current.

Scope makeCurrent();

Returns: A Scope that must be closed to restore the previous context

This method is equivalent to Context.current().with(this).makeCurrent().

Usage Example:

// Assuming Span implements ImplicitContextKeyed
Span span = tracer.spanBuilder("operation").startSpan();

try (Scope scope = span.makeCurrent()) {
    // Span is now available in current context
    performOperation();
} // Previous context restored

Store in Context

Returns a new context with this value stored in it.

Context storeInContext(Context context);

Parameters:

  • context - The context to store this value in

Returns: A new Context containing this value

It's generally recommended to use Context.with(ImplicitContextKeyed) instead of calling this method directly.

Usage Example:

Span span = tracer.spanBuilder("operation").startSpan();

// These are equivalent:
Context contextWithSpan1 = Context.current().with(span);
Context contextWithSpan2 = span.storeInContext(Context.current());

Implementation Examples

Trace Span Implementation

public class TraceSpan implements ImplicitContextKeyed {
    private static final ContextKey<TraceSpan> SPAN_KEY = ContextKey.named("span");
    
    private final String spanId;
    private final String operationName;
    
    public TraceSpan(String spanId, String operationName) {
        this.spanId = spanId;
        this.operationName = operationName;
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(SPAN_KEY, this);
    }
    
    // Static method to get current span
    public static TraceSpan current() {
        return Context.current().get(SPAN_KEY);
    }
    
    // Span-specific methods
    public void setTag(String key, String value) {
        // Implementation
    }
    
    public void end() {
        // Implementation
    }
}

// Usage
TraceSpan span = new TraceSpan("span-123", "database-query");
try (Scope scope = span.makeCurrent()) {
    span.setTag("query", "SELECT * FROM users");
    performDatabaseQuery();
    span.end();
}

Request Context Implementation

public class RequestContext implements ImplicitContextKeyed {
    private static final ContextKey<RequestContext> REQUEST_KEY = ContextKey.named("request");
    
    private final String requestId;
    private final String userId;
    private final Map<String, String> attributes;
    
    public RequestContext(String requestId, String userId) {
        this.requestId = requestId;
        this.userId = userId;
        this.attributes = new ConcurrentHashMap<>();
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(REQUEST_KEY, this);
    }
    
    public static RequestContext current() {
        return Context.current().get(REQUEST_KEY);
    }
    
    // Request-specific methods
    public String getRequestId() { return requestId; }
    public String getUserId() { return userId; }
    
    public void setAttribute(String key, String value) {
        attributes.put(key, value);
    }
    
    public String getAttribute(String key) {
        return attributes.get(key);
    }
}

// Usage
RequestContext request = new RequestContext("req-456", "user-789");
try (Scope scope = request.makeCurrent()) {
    request.setAttribute("action", "login");
    processRequest();
}

Baggage Implementation

public class Baggage implements ImplicitContextKeyed {
    private static final ContextKey<Baggage> BAGGAGE_KEY = ContextKey.named("baggage");
    
    private final Map<String, String> values;
    
    private Baggage(Map<String, String> values) {
        this.values = Collections.unmodifiableMap(new HashMap<>(values));
    }
    
    public static Baggage empty() {
        return new Baggage(Collections.emptyMap());
    }
    
    public static Baggage current() {
        Baggage baggage = Context.current().get(BAGGAGE_KEY);
        return baggage != null ? baggage : empty();
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(BAGGAGE_KEY, this);
    }
    
    public Baggage put(String key, String value) {
        Map<String, String> newValues = new HashMap<>(values);
        newValues.put(key, value);
        return new Baggage(newValues);
    }
    
    public String get(String key) {
        return values.get(key);
    }
    
    public Set<String> keys() {
        return values.keySet();
    }
}

// Usage
Baggage baggage = Baggage.current()
    .put("user-type", "premium")
    .put("region", "us-west");

try (Scope scope = baggage.makeCurrent()) {
    // Baggage values available throughout call chain
    processWithBaggage();
}

Usage Patterns

Builder Pattern with Context

public class OperationBuilder {
    private String operationName;
    private Map<String, String> tags = new HashMap<>();
    
    public OperationBuilder setName(String name) {
        this.operationName = name;
        return this;
    }
    
    public OperationBuilder addTag(String key, String value) {
        tags.put(key, value);
        return this;
    }
    
    public Operation build() {
        return new Operation(operationName, tags);
    }
    
    public Scope buildAndMakeCurrent() {
        return build().makeCurrent();
    }
}

// Usage
try (Scope scope = new OperationBuilder()
        .setName("user-registration")
        .addTag("method", "email")
        .buildAndMakeCurrent()) {
    
    registerUser();
}

Context Chaining with Implicit Values

public void processUserRequest(String userId, String requestId) {
    // Chain multiple implicit context values
    RequestContext request = new RequestContext(requestId, userId);
    TraceSpan span = new TraceSpan("span-123", "process-request");
    
    Context enrichedContext = Context.current()
        .with(request)  // Uses ImplicitContextKeyed.storeInContext()
        .with(span);    // Uses ImplicitContextKeyed.storeInContext()
    
    try (Scope scope = enrichedContext.makeCurrent()) {
        // Both request and span are available
        RequestContext currentRequest = RequestContext.current();
        TraceSpan currentSpan = TraceSpan.current();
        
        performOperation();
    }
}

Conditional Context Application

public class ConditionalContextManager {
    public static Scope applyUserContext(String userId) {
        if (userId != null && !userId.isEmpty()) {
            UserContext userContext = new UserContext(userId);
            return userContext.makeCurrent();
        } else {
            return Scope.noop();
        }
    }
}

// Usage
try (Scope scope = ConditionalContextManager.applyUserContext(getUserId())) {
    // User context applied only if user ID is present
    performOperation();
}

Advanced Patterns

Hierarchical Context Values

public class HierarchicalContext implements ImplicitContextKeyed {
    private static final ContextKey<HierarchicalContext> KEY = ContextKey.named("hierarchical");
    
    private final String level;
    private final HierarchicalContext parent;
    
    private HierarchicalContext(String level, HierarchicalContext parent) {
        this.level = level;
        this.parent = parent;
    }
    
    public static HierarchicalContext root(String level) {
        return new HierarchicalContext(level, null);
    }
    
    public HierarchicalContext child(String childLevel) {
        return new HierarchicalContext(childLevel, this);
    }
    
    public static HierarchicalContext current() {
        return Context.current().get(KEY);
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(KEY, this);
    }
    
    public List<String> getHierarchy() {
        List<String> hierarchy = new ArrayList<>();
        HierarchicalContext current = this;
        while (current != null) {
            hierarchy.add(0, current.level); // Add to front
            current = current.parent;
        }
        return hierarchy;
    }
}

// Usage
HierarchicalContext root = HierarchicalContext.root("application");
try (Scope rootScope = root.makeCurrent()) {
    
    HierarchicalContext service = HierarchicalContext.current().child("user-service");
    try (Scope serviceScope = service.makeCurrent()) {
        
        HierarchicalContext operation = HierarchicalContext.current().child("get-user");
        try (Scope operationScope = operation.makeCurrent()) {
            
            List<String> hierarchy = HierarchicalContext.current().getHierarchy();
            // hierarchy = ["application", "user-service", "get-user"]
        }
    }
}

Context Value Composition

public class CompositeContext implements ImplicitContextKeyed {
    private static final ContextKey<CompositeContext> KEY = ContextKey.named("composite");
    
    private final Map<String, Object> values;
    
    private CompositeContext(Map<String, Object> values) {
        this.values = Collections.unmodifiableMap(new HashMap<>(values));
    }
    
    public static CompositeContext empty() {
        return new CompositeContext(Collections.emptyMap());
    }
    
    public static CompositeContext current() {
        CompositeContext context = Context.current().get(KEY);
        return context != null ? context : empty();
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(KEY, this);
    }
    
    public <T> CompositeContext with(String key, T value) {
        Map<String, Object> newValues = new HashMap<>(values);
        newValues.put(key, value);
        return new CompositeContext(newValues);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> type) {
        Object value = values.get(key);
        return type.isInstance(value) ? (T) value : null;
    }
}

// Usage
CompositeContext composite = CompositeContext.current()
    .with("requestId", "req-123")
    .with("timestamp", System.currentTimeMillis())
    .with("feature-flags", Set.of("feature-a", "feature-b"));

try (Scope scope = composite.makeCurrent()) {
    String requestId = CompositeContext.current().get("requestId", String.class);
    Long timestamp = CompositeContext.current().get("timestamp", Long.class);
    Set<String> flags = CompositeContext.current().get("feature-flags", Set.class);
}

Best Practices

  1. Use private context keys - Hide implementation details from users
  2. Provide static current() methods - Make it easy to access current instance
  3. Make objects immutable - Follow context immutability patterns
  4. Use descriptive key names - Aid in debugging and logging
  5. Handle null contexts gracefully - Provide sensible defaults
  6. Document thread safety - Clarify concurrency expectations
// Good implementation example
public class WellDesignedContext implements ImplicitContextKeyed {
    private static final ContextKey<WellDesignedContext> KEY = 
        ContextKey.named("well-designed-context");
    
    // Immutable fields
    private final String id;
    private final long timestamp;
    
    public WellDesignedContext(String id) {
        this.id = Objects.requireNonNull(id, "id must not be null");
        this.timestamp = System.currentTimeMillis();
    }
    
    // Thread-safe static access
    public static WellDesignedContext current() {
        WellDesignedContext context = Context.current().get(KEY);
        if (context == null) {
            throw new IllegalStateException("No WellDesignedContext in current context");
        }
        return context;
    }
    
    // Safe static access with default
    public static WellDesignedContext currentOrDefault() {
        WellDesignedContext context = Context.current().get(KEY);
        return context != null ? context : new WellDesignedContext("default");
    }
    
    @Override
    public Scope makeCurrent() {
        return Context.current().with(this).makeCurrent();
    }
    
    @Override
    public Context storeInContext(Context context) {
        return context.with(KEY, this);
    }
    
    // Immutable accessors
    public String getId() { return id; }
    public long getTimestamp() { return timestamp; }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-opentelemetry--opentelemetry-context

docs

context-keys-scoping.md

context-propagation.md

core-context.md

executor-integration.md

function-wrapping.md

implicit-context-values.md

index.md

storage-customization.md

tile.json