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

storage-customization.mddocs/

Storage Customization

Context storage customization allows you to plug in different context management strategies, add hooks for context lifecycle events, and implement debugging features. The storage system is designed to be extensible while maintaining thread safety and performance.

Context Storage Interface

Get Current Storage

Returns the currently configured context storage implementation.

static ContextStorage get();

Returns: The active ContextStorage instance

Usage Example:

// Get current storage (usually for advanced use cases)
ContextStorage currentStorage = ContextStorage.get();
Context current = currentStorage.current();

// Attach context directly through storage
try (Scope scope = currentStorage.attach(someContext)) {
    // Context is active
}

Get Default Storage

Returns the default ThreadLocal-based context storage.

static ContextStorage defaultStorage();

Usage Example:

// Get default storage for wrapping or comparison
ContextStorage defaultStorage = ContextStorage.defaultStorage();

// Use as base for custom implementation
public class CustomStorage implements ContextStorage {
    private final ContextStorage delegate = ContextStorage.defaultStorage();
    // ... custom implementation
}

Add Storage Wrapper

Adds a wrapper function that will be applied when storage is first used.

static void addWrapper(Function<? super ContextStorage, ? extends ContextStorage> wrapper);

Parameters:

  • wrapper - Function that wraps the storage implementation

Usage Example:

// Must be called early in application lifecycle
static {
    ContextStorage.addWrapper(storage -> new LoggingContextStorage(storage));
    ContextStorage.addWrapper(storage -> new MetricsContextStorage(storage));
}

// Wrappers are applied in order: MetricsContextStorage(LoggingContextStorage(original))

Storage Operations

Attach Context

Sets the specified context as current and returns a scope for cleanup.

Scope attach(Context toAttach);

Parameters:

  • toAttach - The context to make current

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

Get Current Context

Returns the current context, or null if none is attached.

Context current();

Returns: The current Context, or null

Get Root Context

Returns the root context for this storage implementation.

Context root();

Returns: The root Context instance

Custom Storage Implementations

Logging Context Storage

A wrapper that logs all context operations for debugging.

public class LoggingContextStorage implements ContextStorage {
    private static final Logger logger = LoggerFactory.getLogger(LoggingContextStorage.class);
    private final ContextStorage delegate;
    
    public LoggingContextStorage(ContextStorage delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Scope attach(Context toAttach) {
        logger.debug("Attaching context: {}", toAttach);
        
        Context previous = delegate.current();
        Scope scope = delegate.attach(toAttach);
        
        return new LoggingScope(scope, previous, toAttach);
    }
    
    @Override
    public Context current() {
        Context current = delegate.current();
        logger.trace("Current context: {}", current);
        return current;
    }
    
    @Override
    public Context root() {
        return delegate.root();
    }
    
    private static class LoggingScope implements Scope {
        private final Scope delegate;
        private final Context previous;
        private final Context attached;
        
        LoggingScope(Scope delegate, Context previous, Context attached) {
            this.delegate = delegate;
            this.previous = previous;
            this.attached = attached;
        }
        
        @Override
        public void close() {
            logger.debug("Closing scope, restoring context from {} to {}", attached, previous);
            delegate.close();
        }
    }
}

// Register early in application
static {
    ContextStorage.addWrapper(LoggingContextStorage::new);
}

MDC Context Storage

A wrapper that syncs context values with SLF4J MDC (Mapped Diagnostic Context).

public class MdcContextStorage implements ContextStorage {
    private final ContextStorage delegate;
    private static final ContextKey<Map<String, String>> MDC_KEY = 
        ContextKey.named("mdc-values");
    
    public MdcContextStorage(ContextStorage delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public Scope attach(Context toAttach) {
        // Extract MDC values from context
        Map<String, String> mdcValues = toAttach.get(MDC_KEY);
        Map<String, String> previousMdc = null;
        
        if (mdcValues != null) {
            // Backup current MDC
            previousMdc = MDC.getCopyOfContextMap();
            
            // Clear and set new values
            MDC.clear();
            for (Map.Entry<String, String> entry : mdcValues.entrySet()) {
                MDC.put(entry.getKey(), entry.getValue());
            }
        }
        
        Scope delegate = this.delegate.attach(toAttach);
        return new MdcScope(delegate, previousMdc);
    }
    
    @Override
    public Context current() {
        return delegate.current();
    }
    
    @Override
    public Context root() {
        return delegate.root();
    }
    
    private static class MdcScope implements Scope {
        private final Scope delegate;
        private final Map<String, String> previousMdc;
        
        MdcScope(Scope delegate, Map<String, String> previousMdc) {
            this.delegate = delegate;
            this.previousMdc = previousMdc;
        }
        
        @Override
        public void close() {
            // Restore previous MDC state
            MDC.clear();
            if (previousMdc != null) {
                MDC.setContextMap(previousMdc);
            }
            delegate.close();
        }
    }
    
    // Helper method to add MDC values to context
    public static Context withMdc(Context context, String key, String value) {
        Map<String, String> mdcValues = context.get(MDC_KEY);
        if (mdcValues == null) {
            mdcValues = new HashMap<>();
        } else {
            mdcValues = new HashMap<>(mdcValues); // Copy for immutability
        }
        mdcValues.put(key, value);
        return context.with(MDC_KEY, mdcValues);
    }
}

Metrics Context Storage

A wrapper that collects metrics on context operations.

public class MetricsContextStorage implements ContextStorage {
    private final ContextStorage delegate;
    private final Counter attachCount;
    private final Timer attachTimer;
    private final Gauge currentDepth;
    private final AtomicLong depthCounter = new AtomicLong(0);
    
    public MetricsContextStorage(ContextStorage delegate) {
        this.delegate = delegate;
        // Initialize metrics (using Micrometer as example)
        MeterRegistry registry = Metrics.globalRegistry;
        this.attachCount = Counter.builder("context.attach.count")
            .description("Number of context attachments")
            .register(registry);
        this.attachTimer = Timer.builder("context.attach.duration")
            .description("Time spent attaching contexts")
            .register(registry);
        this.currentDepth = Gauge.builder("context.depth.current")
            .description("Current context depth")
            .register(registry, depthCounter, AtomicLong::get);
    }
    
    @Override
    public Scope attach(Context toAttach) {
        attachCount.increment();
        Timer.Sample sample = Timer.start();
        
        depthCounter.incrementAndGet();
        Scope scope = delegate.attach(toAttach);
        sample.stop(attachTimer);
        
        return new MetricsScope(scope, depthCounter);
    }
    
    @Override
    public Context current() {
        return delegate.current();
    }
    
    @Override
    public Context root() {
        return delegate.root();
    }
    
    private static class MetricsScope implements Scope {
        private final Scope delegate;
        private final AtomicLong depthCounter;
        
        MetricsScope(Scope delegate, AtomicLong depthCounter) {
            this.delegate = delegate;
            this.depthCounter = depthCounter;
        }
        
        @Override
        public void close() {
            depthCounter.decrementAndGet();
            delegate.close();
        }
    }
}

Context Storage Providers

Storage Provider Interface

Interface for providing custom storage implementations.

interface ContextStorageProvider {
    ContextStorage get();
}

Custom Storage Provider

public class CustomStorageProvider implements ContextStorageProvider {
    @Override
    public ContextStorage get() {
        ContextStorage base = ContextStorage.defaultStorage();
        
        // Apply multiple wrappers
        return new MetricsContextStorage(
            new LoggingContextStorage(
                new MdcContextStorage(base)
            )
        );
    }
}

// Register via ServiceLoader
// Create META-INF/services/io.opentelemetry.context.ContextStorageProvider
// Add line: com.example.CustomStorageProvider

Strict Context Storage

OpenTelemetry provides a strict context storage implementation for debugging context propagation issues.

Enabling Strict Context

Enable strict context checking with JVM argument:

java -Dio.opentelemetry.context.enableStrictContext=true your.Application

Features:

  • Ensures scopes are closed on the correct thread
  • Detects scope garbage collection before closing
  • Validates proper scope nesting
  • Detects invalid usage in Kotlin coroutines

Usage Example:

// This will trigger warnings in strict mode
Context context = Context.current().with(KEY, "value");
Scope scope = context.makeCurrent();

// BAD: Scope not closed in try-with-resources
performOperation();
scope.close(); // Warning: not closed properly

// BAD: Wrong thread
Thread thread = new Thread(() -> {
    scope.close(); // Error: closed on wrong thread
});
thread.start();

Advanced Storage Patterns

Conditional Storage Wrapping

public class ConditionalStorageWrapper {
    public static void configure() {
        ContextStorage.addWrapper(storage -> {
            // Only add logging in development
            if (isDevelopmentMode()) {
                storage = new LoggingContextStorage(storage);
            }
            
            // Always add metrics
            storage = new MetricsContextStorage(storage);
            
            // Add audit trail in production
            if (isProductionMode()) {
                storage = new AuditContextStorage(storage);
            }
            
            return storage;
        });
    }
}

Context Migration Storage

public class MigrationContextStorage implements ContextStorage {
    private final ContextStorage newStorage;
    private final LegacyContextStorage legacyStorage;
    
    public MigrationContextStorage(ContextStorage newStorage, LegacyContextStorage legacyStorage) {
        this.newStorage = newStorage;
        this.legacyStorage = legacyStorage;
    }
    
    @Override
    public Scope attach(Context toAttach) {
        // Migrate legacy context values
        Context migratedContext = migrateLegacyValues(toAttach);
        
        // Attach to both storages during migration
        Scope newScope = newStorage.attach(migratedContext);
        Scope legacyScope = legacyStorage.attachLegacy(migratedContext);
        
        return new MigrationScope(newScope, legacyScope);
    }
    
    private Context migrateLegacyValues(Context context) {
        // Extract legacy values and convert to new format
        // Implementation depends on legacy system
        return context;
    }
}

Storage Performance Considerations

  • Wrappers add overhead - use judiciously in production
  • Avoid expensive operations in attach/close methods
  • Consider using ThreadLocal for frequently accessed data
  • Batch metrics updates when possible
  • Use appropriate logging levels to avoid performance impact
// Good: Minimal overhead wrapper
public class EfficientWrapper implements ContextStorage {
    private final ContextStorage delegate;
    private static final AtomicLong counter = new AtomicLong();
    
    @Override
    public Scope attach(Context toAttach) {
        counter.incrementAndGet(); // Fast atomic operation
        return new CountingScope(delegate.attach(toAttach));
    }
    
    private static class CountingScope implements Scope {
        private final Scope delegate;
        
        @Override
        public void close() {
            counter.decrementAndGet();
            delegate.close();
        }
    }
}

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