or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-keys-scoping.mdcontext-propagation.mdcore-context.mdexecutor-integration.mdfunction-wrapping.mdimplicit-context-values.mdindex.mdstorage-customization.md

storage-customization.mddocs/

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

```