0
# Storage Customization
1
2
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.
3
4
## Context Storage Interface
5
6
### Get Current Storage
7
8
Returns the currently configured context storage implementation.
9
10
```java { .api }
11
static ContextStorage get();
12
```
13
14
**Returns:** The active ContextStorage instance
15
16
**Usage Example:**
17
```java
18
// Get current storage (usually for advanced use cases)
19
ContextStorage currentStorage = ContextStorage.get();
20
Context current = currentStorage.current();
21
22
// Attach context directly through storage
23
try (Scope scope = currentStorage.attach(someContext)) {
24
// Context is active
25
}
26
```
27
28
### Get Default Storage
29
30
Returns the default ThreadLocal-based context storage.
31
32
```java { .api }
33
static ContextStorage defaultStorage();
34
```
35
36
**Usage Example:**
37
```java
38
// Get default storage for wrapping or comparison
39
ContextStorage defaultStorage = ContextStorage.defaultStorage();
40
41
// Use as base for custom implementation
42
public class CustomStorage implements ContextStorage {
43
private final ContextStorage delegate = ContextStorage.defaultStorage();
44
// ... custom implementation
45
}
46
```
47
48
### Add Storage Wrapper
49
50
Adds a wrapper function that will be applied when storage is first used.
51
52
```java { .api }
53
static void addWrapper(Function<? super ContextStorage, ? extends ContextStorage> wrapper);
54
```
55
56
**Parameters:**
57
- `wrapper` - Function that wraps the storage implementation
58
59
**Usage Example:**
60
```java
61
// Must be called early in application lifecycle
62
static {
63
ContextStorage.addWrapper(storage -> new LoggingContextStorage(storage));
64
ContextStorage.addWrapper(storage -> new MetricsContextStorage(storage));
65
}
66
67
// Wrappers are applied in order: MetricsContextStorage(LoggingContextStorage(original))
68
```
69
70
## Storage Operations
71
72
### Attach Context
73
74
Sets the specified context as current and returns a scope for cleanup.
75
76
```java { .api }
77
Scope attach(Context toAttach);
78
```
79
80
**Parameters:**
81
- `toAttach` - The context to make current
82
83
**Returns:** A Scope that must be closed to restore previous context
84
85
### Get Current Context
86
87
Returns the current context, or null if none is attached.
88
89
```java { .api }
90
Context current();
91
```
92
93
**Returns:** The current Context, or null
94
95
### Get Root Context
96
97
Returns the root context for this storage implementation.
98
99
```java { .api }
100
Context root();
101
```
102
103
**Returns:** The root Context instance
104
105
## Custom Storage Implementations
106
107
### Logging Context Storage
108
109
A wrapper that logs all context operations for debugging.
110
111
```java
112
public class LoggingContextStorage implements ContextStorage {
113
private static final Logger logger = LoggerFactory.getLogger(LoggingContextStorage.class);
114
private final ContextStorage delegate;
115
116
public LoggingContextStorage(ContextStorage delegate) {
117
this.delegate = delegate;
118
}
119
120
@Override
121
public Scope attach(Context toAttach) {
122
logger.debug("Attaching context: {}", toAttach);
123
124
Context previous = delegate.current();
125
Scope scope = delegate.attach(toAttach);
126
127
return new LoggingScope(scope, previous, toAttach);
128
}
129
130
@Override
131
public Context current() {
132
Context current = delegate.current();
133
logger.trace("Current context: {}", current);
134
return current;
135
}
136
137
@Override
138
public Context root() {
139
return delegate.root();
140
}
141
142
private static class LoggingScope implements Scope {
143
private final Scope delegate;
144
private final Context previous;
145
private final Context attached;
146
147
LoggingScope(Scope delegate, Context previous, Context attached) {
148
this.delegate = delegate;
149
this.previous = previous;
150
this.attached = attached;
151
}
152
153
@Override
154
public void close() {
155
logger.debug("Closing scope, restoring context from {} to {}", attached, previous);
156
delegate.close();
157
}
158
}
159
}
160
161
// Register early in application
162
static {
163
ContextStorage.addWrapper(LoggingContextStorage::new);
164
}
165
```
166
167
### MDC Context Storage
168
169
A wrapper that syncs context values with SLF4J MDC (Mapped Diagnostic Context).
170
171
```java
172
public class MdcContextStorage implements ContextStorage {
173
private final ContextStorage delegate;
174
private static final ContextKey<Map<String, String>> MDC_KEY =
175
ContextKey.named("mdc-values");
176
177
public MdcContextStorage(ContextStorage delegate) {
178
this.delegate = delegate;
179
}
180
181
@Override
182
public Scope attach(Context toAttach) {
183
// Extract MDC values from context
184
Map<String, String> mdcValues = toAttach.get(MDC_KEY);
185
Map<String, String> previousMdc = null;
186
187
if (mdcValues != null) {
188
// Backup current MDC
189
previousMdc = MDC.getCopyOfContextMap();
190
191
// Clear and set new values
192
MDC.clear();
193
for (Map.Entry<String, String> entry : mdcValues.entrySet()) {
194
MDC.put(entry.getKey(), entry.getValue());
195
}
196
}
197
198
Scope delegate = this.delegate.attach(toAttach);
199
return new MdcScope(delegate, previousMdc);
200
}
201
202
@Override
203
public Context current() {
204
return delegate.current();
205
}
206
207
@Override
208
public Context root() {
209
return delegate.root();
210
}
211
212
private static class MdcScope implements Scope {
213
private final Scope delegate;
214
private final Map<String, String> previousMdc;
215
216
MdcScope(Scope delegate, Map<String, String> previousMdc) {
217
this.delegate = delegate;
218
this.previousMdc = previousMdc;
219
}
220
221
@Override
222
public void close() {
223
// Restore previous MDC state
224
MDC.clear();
225
if (previousMdc != null) {
226
MDC.setContextMap(previousMdc);
227
}
228
delegate.close();
229
}
230
}
231
232
// Helper method to add MDC values to context
233
public static Context withMdc(Context context, String key, String value) {
234
Map<String, String> mdcValues = context.get(MDC_KEY);
235
if (mdcValues == null) {
236
mdcValues = new HashMap<>();
237
} else {
238
mdcValues = new HashMap<>(mdcValues); // Copy for immutability
239
}
240
mdcValues.put(key, value);
241
return context.with(MDC_KEY, mdcValues);
242
}
243
}
244
```
245
246
### Metrics Context Storage
247
248
A wrapper that collects metrics on context operations.
249
250
```java
251
public class MetricsContextStorage implements ContextStorage {
252
private final ContextStorage delegate;
253
private final Counter attachCount;
254
private final Timer attachTimer;
255
private final Gauge currentDepth;
256
private final AtomicLong depthCounter = new AtomicLong(0);
257
258
public MetricsContextStorage(ContextStorage delegate) {
259
this.delegate = delegate;
260
// Initialize metrics (using Micrometer as example)
261
MeterRegistry registry = Metrics.globalRegistry;
262
this.attachCount = Counter.builder("context.attach.count")
263
.description("Number of context attachments")
264
.register(registry);
265
this.attachTimer = Timer.builder("context.attach.duration")
266
.description("Time spent attaching contexts")
267
.register(registry);
268
this.currentDepth = Gauge.builder("context.depth.current")
269
.description("Current context depth")
270
.register(registry, depthCounter, AtomicLong::get);
271
}
272
273
@Override
274
public Scope attach(Context toAttach) {
275
attachCount.increment();
276
Timer.Sample sample = Timer.start();
277
278
depthCounter.incrementAndGet();
279
Scope scope = delegate.attach(toAttach);
280
sample.stop(attachTimer);
281
282
return new MetricsScope(scope, depthCounter);
283
}
284
285
@Override
286
public Context current() {
287
return delegate.current();
288
}
289
290
@Override
291
public Context root() {
292
return delegate.root();
293
}
294
295
private static class MetricsScope implements Scope {
296
private final Scope delegate;
297
private final AtomicLong depthCounter;
298
299
MetricsScope(Scope delegate, AtomicLong depthCounter) {
300
this.delegate = delegate;
301
this.depthCounter = depthCounter;
302
}
303
304
@Override
305
public void close() {
306
depthCounter.decrementAndGet();
307
delegate.close();
308
}
309
}
310
}
311
```
312
313
## Context Storage Providers
314
315
### Storage Provider Interface
316
317
Interface for providing custom storage implementations.
318
319
```java { .api }
320
interface ContextStorageProvider {
321
ContextStorage get();
322
}
323
```
324
325
### Custom Storage Provider
326
327
```java
328
public class CustomStorageProvider implements ContextStorageProvider {
329
@Override
330
public ContextStorage get() {
331
ContextStorage base = ContextStorage.defaultStorage();
332
333
// Apply multiple wrappers
334
return new MetricsContextStorage(
335
new LoggingContextStorage(
336
new MdcContextStorage(base)
337
)
338
);
339
}
340
}
341
342
// Register via ServiceLoader
343
// Create META-INF/services/io.opentelemetry.context.ContextStorageProvider
344
// Add line: com.example.CustomStorageProvider
345
```
346
347
## Strict Context Storage
348
349
OpenTelemetry provides a strict context storage implementation for debugging context propagation issues.
350
351
### Enabling Strict Context
352
353
Enable strict context checking with JVM argument:
354
355
```bash
356
java -Dio.opentelemetry.context.enableStrictContext=true your.Application
357
```
358
359
**Features:**
360
- Ensures scopes are closed on the correct thread
361
- Detects scope garbage collection before closing
362
- Validates proper scope nesting
363
- Detects invalid usage in Kotlin coroutines
364
365
**Usage Example:**
366
```java
367
// This will trigger warnings in strict mode
368
Context context = Context.current().with(KEY, "value");
369
Scope scope = context.makeCurrent();
370
371
// BAD: Scope not closed in try-with-resources
372
performOperation();
373
scope.close(); // Warning: not closed properly
374
375
// BAD: Wrong thread
376
Thread thread = new Thread(() -> {
377
scope.close(); // Error: closed on wrong thread
378
});
379
thread.start();
380
```
381
382
## Advanced Storage Patterns
383
384
### Conditional Storage Wrapping
385
386
```java
387
public class ConditionalStorageWrapper {
388
public static void configure() {
389
ContextStorage.addWrapper(storage -> {
390
// Only add logging in development
391
if (isDevelopmentMode()) {
392
storage = new LoggingContextStorage(storage);
393
}
394
395
// Always add metrics
396
storage = new MetricsContextStorage(storage);
397
398
// Add audit trail in production
399
if (isProductionMode()) {
400
storage = new AuditContextStorage(storage);
401
}
402
403
return storage;
404
});
405
}
406
}
407
```
408
409
### Context Migration Storage
410
411
```java
412
public class MigrationContextStorage implements ContextStorage {
413
private final ContextStorage newStorage;
414
private final LegacyContextStorage legacyStorage;
415
416
public MigrationContextStorage(ContextStorage newStorage, LegacyContextStorage legacyStorage) {
417
this.newStorage = newStorage;
418
this.legacyStorage = legacyStorage;
419
}
420
421
@Override
422
public Scope attach(Context toAttach) {
423
// Migrate legacy context values
424
Context migratedContext = migrateLegacyValues(toAttach);
425
426
// Attach to both storages during migration
427
Scope newScope = newStorage.attach(migratedContext);
428
Scope legacyScope = legacyStorage.attachLegacy(migratedContext);
429
430
return new MigrationScope(newScope, legacyScope);
431
}
432
433
private Context migrateLegacyValues(Context context) {
434
// Extract legacy values and convert to new format
435
// Implementation depends on legacy system
436
return context;
437
}
438
}
439
```
440
441
## Storage Performance Considerations
442
443
- Wrappers add overhead - use judiciously in production
444
- Avoid expensive operations in attach/close methods
445
- Consider using ThreadLocal for frequently accessed data
446
- Batch metrics updates when possible
447
- Use appropriate logging levels to avoid performance impact
448
449
```java
450
// Good: Minimal overhead wrapper
451
public class EfficientWrapper implements ContextStorage {
452
private final ContextStorage delegate;
453
private static final AtomicLong counter = new AtomicLong();
454
455
@Override
456
public Scope attach(Context toAttach) {
457
counter.incrementAndGet(); // Fast atomic operation
458
return new CountingScope(delegate.attach(toAttach));
459
}
460
461
private static class CountingScope implements Scope {
462
private final Scope delegate;
463
464
@Override
465
public void close() {
466
counter.decrementAndGet();
467
delegate.close();
468
}
469
}
470
}
471
```