0
# Cross-Service Propagation
1
2
Comprehensive context propagation system for distributed tracing and baggage across service boundaries. OpenTelemetry propagation enables automatic context extraction and injection using standard HTTP headers with support for W3C Trace Context and W3C Baggage specifications.
3
4
## Capabilities
5
6
### Global Propagation Operations
7
8
Extract and inject context using the globally configured propagator.
9
10
```python { .api }
11
def extract(
12
carrier: CarrierT,
13
context: Optional[Context] = None,
14
getter: Getter[CarrierT] = default_getter,
15
) -> Context:
16
"""
17
Uses the configured propagator to extract a Context from the carrier.
18
19
Parameters:
20
- carrier: Object containing values used to construct a Context
21
- context: Optional Context to use, defaults to root context if not set
22
- getter: Object that can retrieve values from the carrier
23
24
Returns:
25
Context extracted from the carrier
26
"""
27
28
def inject(
29
carrier: CarrierT,
30
context: Optional[Context] = None,
31
setter: Setter[CarrierT] = default_setter,
32
) -> None:
33
"""
34
Uses the configured propagator to inject a Context into the carrier.
35
36
Parameters:
37
- carrier: Medium used by propagators to write values
38
- context: Optional Context to use, defaults to current context if not set
39
- setter: Object that can set values on the carrier
40
"""
41
42
def get_global_textmap() -> TextMapPropagator:
43
"""
44
Returns the global text map propagator.
45
46
Returns:
47
The current global TextMapPropagator instance
48
"""
49
50
def set_global_textmap(http_text_format: TextMapPropagator) -> None:
51
"""
52
Sets the global text map propagator.
53
54
Parameters:
55
- http_text_format: The TextMapPropagator to set as global
56
"""
57
```
58
59
### Text Map Propagator Interface
60
61
Base interface for implementing propagators that work with text-based carriers.
62
63
```python { .api }
64
class TextMapPropagator(ABC):
65
"""Abstract base class for text map propagators."""
66
67
def extract(
68
self,
69
carrier: CarrierT,
70
context: Optional[Context] = None,
71
getter: Getter[CarrierT] = default_getter,
72
) -> Context:
73
"""
74
Extract context from a carrier.
75
76
Parameters:
77
- carrier: The carrier containing context data
78
- context: Optional context to merge extracted data into
79
- getter: Object to retrieve values from carrier
80
81
Returns:
82
Context with extracted data
83
"""
84
85
def inject(
86
self,
87
carrier: CarrierT,
88
context: Optional[Context] = None,
89
setter: Setter[CarrierT] = default_setter,
90
) -> None:
91
"""
92
Inject context into a carrier.
93
94
Parameters:
95
- carrier: The carrier to inject context into
96
- context: Optional context to inject, defaults to current context
97
- setter: Object to set values in carrier
98
"""
99
100
@property
101
def fields(self) -> Set[str]:
102
"""
103
Returns the fields this propagator will read or write.
104
105
Returns:
106
Set of field names used by this propagator
107
"""
108
```
109
110
### Carrier Access Patterns
111
112
Generic interfaces for reading from and writing to different carrier types.
113
114
```python { .api }
115
class Getter(Generic[CarrierT], ABC):
116
"""Abstract base class for retrieving values from carriers."""
117
118
def get(self, carrier: CarrierT, key: str) -> Optional[List[str]]:
119
"""
120
Retrieve the value(s) associated with the key from the carrier.
121
122
Parameters:
123
- carrier: The carrier to retrieve values from
124
- key: The key of the value to retrieve
125
126
Returns:
127
List of values associated with the key, or None if not found
128
"""
129
130
def keys(self, carrier: CarrierT) -> List[str]:
131
"""
132
Retrieve all keys from the carrier.
133
134
Parameters:
135
- carrier: The carrier to retrieve keys from
136
137
Returns:
138
List of all keys in the carrier
139
"""
140
141
class Setter(Generic[CarrierT], ABC):
142
"""Abstract base class for setting values in carriers."""
143
144
def set(self, carrier: CarrierT, key: str, value: str) -> None:
145
"""
146
Set a value in the carrier.
147
148
Parameters:
149
- carrier: The carrier to set the value in
150
- key: The key to set
151
- value: The value to set
152
"""
153
```
154
155
### Default Carrier Implementations
156
157
Default implementations for common carrier patterns.
158
159
```python { .api }
160
class DefaultGetter(Getter[Dict[str, str]]):
161
"""Default getter for dict-like carriers."""
162
163
def get(self, carrier: Dict[str, str], key: str) -> Optional[List[str]]:
164
"""Get value from dict carrier."""
165
166
def keys(self, carrier: Dict[str, str]) -> List[str]:
167
"""Get all keys from dict carrier."""
168
169
class DefaultSetter(Setter[Dict[str, str]]):
170
"""Default setter for dict-like carriers."""
171
172
def set(self, carrier: Dict[str, str], key: str, value: str) -> None:
173
"""Set value in dict carrier."""
174
175
default_getter: DefaultGetter
176
default_setter: DefaultSetter
177
```
178
179
### Composite Propagator
180
181
Combine multiple propagators for comprehensive context propagation.
182
183
```python { .api }
184
class CompositePropagator(TextMapPropagator):
185
"""Propagator that combines multiple propagators."""
186
187
def __init__(self, propagators: Sequence[TextMapPropagator]) -> None:
188
"""
189
Initialize with a list of propagators.
190
191
Parameters:
192
- propagators: Sequence of TextMapPropagator instances to combine
193
"""
194
195
def extract(
196
self,
197
carrier: CarrierT,
198
context: Optional[Context] = None,
199
getter: Getter[CarrierT] = default_getter,
200
) -> Context:
201
"""Extract using all propagators in sequence."""
202
203
def inject(
204
self,
205
carrier: CarrierT,
206
context: Optional[Context] = None,
207
setter: Setter[CarrierT] = default_setter,
208
) -> None:
209
"""Inject using all propagators."""
210
211
@property
212
def fields(self) -> Set[str]:
213
"""Return combined fields from all propagators."""
214
```
215
216
### W3C Trace Context Propagator
217
218
Standard W3C Trace Context propagation implementation.
219
220
```python { .api }
221
class TraceContextTextMapPropagator(TextMapPropagator):
222
"""W3C Trace Context propagator for trace correlation."""
223
224
def extract(
225
self,
226
carrier: CarrierT,
227
context: Optional[Context] = None,
228
getter: Getter[CarrierT] = default_getter,
229
) -> Context:
230
"""
231
Extract trace context from traceparent and tracestate headers.
232
233
Parameters:
234
- carrier: The carrier containing HTTP headers or equivalent
235
- context: Optional context to merge extracted data into
236
- getter: Object to retrieve header values from carrier
237
238
Returns:
239
Context with extracted trace information
240
"""
241
242
def inject(
243
self,
244
carrier: CarrierT,
245
context: Optional[Context] = None,
246
setter: Setter[CarrierT] = default_setter,
247
) -> None:
248
"""
249
Inject trace context into traceparent and tracestate headers.
250
251
Parameters:
252
- carrier: The carrier to inject headers into
253
- context: Optional context to inject, defaults to current context
254
- setter: Object to set header values in carrier
255
"""
256
257
@property
258
def fields(self) -> Set[str]:
259
"""Returns {'traceparent', 'tracestate'}."""
260
```
261
262
### W3C Baggage Propagator
263
264
Standard W3C Baggage propagation implementation.
265
266
```python { .api }
267
class W3CBaggagePropagator(TextMapPropagator):
268
"""W3C Baggage propagator for cross-service data propagation."""
269
270
def extract(
271
self,
272
carrier: CarrierT,
273
context: Optional[Context] = None,
274
getter: Getter[CarrierT] = default_getter,
275
) -> Context:
276
"""
277
Extract baggage from baggage header.
278
279
Parameters:
280
- carrier: The carrier containing HTTP headers or equivalent
281
- context: Optional context to merge extracted data into
282
- getter: Object to retrieve header values from carrier
283
284
Returns:
285
Context with extracted baggage information
286
"""
287
288
def inject(
289
self,
290
carrier: CarrierT,
291
context: Optional[Context] = None,
292
setter: Setter[CarrierT] = default_setter,
293
) -> None:
294
"""
295
Inject baggage into baggage header.
296
297
Parameters:
298
- carrier: The carrier to inject headers into
299
- context: Optional context to inject, defaults to current context
300
- setter: Object to set header values in carrier
301
"""
302
303
@property
304
def fields(self) -> Set[str]:
305
"""Returns {'baggage'}."""
306
```
307
308
### Baggage Operations
309
310
Manage baggage key-value pairs for cross-service context propagation.
311
312
```python { .api }
313
def get_all(context: Optional[Context] = None) -> Mapping[str, object]:
314
"""
315
Returns all name/value pairs in the Baggage.
316
317
Parameters:
318
- context: The Context to use, if not set uses current Context
319
320
Returns:
321
The name/value pairs in the Baggage as a read-only mapping
322
"""
323
324
def get_baggage(name: str, context: Optional[Context] = None) -> Optional[object]:
325
"""
326
Provides access to the value for a name/value pair in the Baggage.
327
328
Parameters:
329
- name: The name of the value to retrieve
330
- context: The Context to use, if not set uses current Context
331
332
Returns:
333
The value associated with the given name, or None if not present
334
"""
335
336
def set_baggage(
337
name: str,
338
value: object,
339
context: Optional[Context] = None
340
) -> Context:
341
"""
342
Sets a value in the Baggage.
343
344
Parameters:
345
- name: The name of the value to set
346
- value: The value to set
347
- context: The Context to use, if not set uses current Context
348
349
Returns:
350
A Context with the value updated
351
"""
352
353
def remove_baggage(name: str, context: Optional[Context] = None) -> Context:
354
"""
355
Removes a value from the Baggage.
356
357
Parameters:
358
- name: The name of the value to remove
359
- context: The Context to use, if not set uses current Context
360
361
Returns:
362
A Context with the name/value removed
363
"""
364
365
def clear(context: Optional[Context] = None) -> Context:
366
"""
367
Removes all values from the Baggage.
368
369
Parameters:
370
- context: The Context to use, if not set uses current Context
371
372
Returns:
373
A Context with all baggage entries removed
374
"""
375
```
376
377
## Usage Examples
378
379
### Basic HTTP Header Propagation
380
381
```python
382
from opentelemetry import propagate, trace, baggage
383
import requests
384
385
# Server side - extract context from incoming request
386
def handle_request(request):
387
# Extract context from HTTP headers
388
context = propagate.extract(request.headers)
389
390
# Activate the extracted context
391
token = context.attach(context)
392
try:
393
# Process request with trace context
394
tracer = trace.get_tracer(__name__)
395
with tracer.start_as_current_span("handle-request") as span:
396
span.set_attribute("http.method", request.method)
397
398
# Baggage is automatically available
399
user_id = baggage.get_baggage("user.id")
400
if user_id:
401
span.set_attribute("user.id", user_id)
402
403
return process_request()
404
finally:
405
context.detach(token)
406
407
# Client side - inject context into outgoing request
408
def make_request():
409
tracer = trace.get_tracer(__name__)
410
with tracer.start_as_current_span("http-request") as span:
411
# Set baggage for propagation
412
ctx = baggage.set_baggage("user.id", "12345")
413
token = context.attach(ctx)
414
415
try:
416
# Prepare outgoing request
417
headers = {}
418
419
# Inject current context into headers
420
propagate.inject(headers)
421
422
# Make request with propagated context
423
response = requests.get(
424
"http://api.example.com/data",
425
headers=headers
426
)
427
428
span.set_attribute("http.status_code", response.status_code)
429
return response.json()
430
finally:
431
context.detach(token)
432
```
433
434
### Custom Carrier Implementation
435
436
```python
437
from opentelemetry import propagate
438
from opentelemetry.propagators.textmap import Getter, Setter
439
from typing import Dict, List, Optional
440
441
# Custom carrier for message queue metadata
442
class MessageMetadata:
443
def __init__(self):
444
self.headers: Dict[str, str] = {}
445
446
def get_header(self, key: str) -> Optional[str]:
447
return self.headers.get(key)
448
449
def set_header(self, key: str, value: str) -> None:
450
self.headers[key] = value
451
452
def get_all_headers(self) -> Dict[str, str]:
453
return self.headers.copy()
454
455
# Custom getter for message metadata
456
class MessageGetter(Getter[MessageMetadata]):
457
def get(self, carrier: MessageMetadata, key: str) -> Optional[List[str]]:
458
value = carrier.get_header(key)
459
return [value] if value is not None else None
460
461
def keys(self, carrier: MessageMetadata) -> List[str]:
462
return list(carrier.get_all_headers().keys())
463
464
# Custom setter for message metadata
465
class MessageSetter(Setter[MessageMetadata]):
466
def set(self, carrier: MessageMetadata, key: str, value: str) -> None:
467
carrier.set_header(key, value)
468
469
# Usage with custom carrier
470
def publish_message(data):
471
metadata = MessageMetadata()
472
473
# Inject context into message metadata
474
propagate.inject(
475
metadata,
476
setter=MessageSetter()
477
)
478
479
# Publish with propagated context
480
message_broker.publish(data, metadata)
481
482
def consume_message(data, metadata):
483
# Extract context from message metadata
484
context = propagate.extract(
485
metadata,
486
getter=MessageGetter()
487
)
488
489
# Process with extracted context
490
token = context.attach(context)
491
try:
492
process_message(data)
493
finally:
494
context.detach(token)
495
```
496
497
### Flask Integration Example
498
499
```python
500
from flask import Flask, request, g
501
from opentelemetry import propagate, trace, baggage
502
from opentelemetry.propagators.textmap import Getter
503
504
app = Flask(__name__)
505
506
# Custom getter for Flask request headers
507
class FlaskRequestGetter(Getter):
508
def get(self, carrier, key: str):
509
return carrier.headers.getlist(key)
510
511
def keys(self, carrier):
512
return list(carrier.headers.keys())
513
514
@app.before_request
515
def before_request():
516
# Extract context from incoming request
517
context = propagate.extract(
518
request,
519
getter=FlaskRequestGetter()
520
)
521
522
# Store context in Flask's g object
523
g.otel_context = context
524
g.otel_token = context.attach(context)
525
526
@app.after_request
527
def after_request(response):
528
# Clean up context
529
if hasattr(g, 'otel_token'):
530
context.detach(g.otel_token)
531
return response
532
533
@app.route('/api/users')
534
def get_users():
535
tracer = trace.get_tracer(__name__)
536
with tracer.start_as_current_span("get-users") as span:
537
# Context and baggage automatically available
538
user_role = baggage.get_baggage("user.role")
539
540
span.set_attribute("http.method", request.method)
541
span.set_attribute("http.url", request.url)
542
543
if user_role:
544
span.set_attribute("user.role", user_role)
545
546
return {"users": get_user_list()}
547
548
def make_external_request(url):
549
"""Make external request with context propagation."""
550
headers = {}
551
552
# Inject current context
553
propagate.inject(headers)
554
555
# Make request
556
response = requests.get(url, headers=headers)
557
return response.json()
558
```
559
560
### Environment-Based Propagator Configuration
561
562
```python
563
import os
564
from opentelemetry import propagate
565
from opentelemetry.propagators.composite import CompositePropagator
566
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
567
from opentelemetry.baggage.propagation import W3CBaggagePropagator
568
569
# Configure propagators based on environment
570
def setup_propagators():
571
# Default propagators
572
propagators = [
573
TraceContextTextMapPropagator(),
574
W3CBaggagePropagator(),
575
]
576
577
# Check environment configuration
578
env_propagators = os.environ.get("OTEL_PROPAGATORS", "tracecontext,baggage")
579
580
if env_propagators == "none":
581
# Disable all propagation
582
propagators = []
583
elif "jaeger" in env_propagators:
584
# Add Jaeger propagator if available
585
try:
586
from opentelemetry.propagators.jaeger import JaegerPropagator
587
propagators.append(JaegerPropagator())
588
except ImportError:
589
pass
590
591
# Set global propagator
592
composite = CompositePropagator(propagators)
593
propagate.set_global_textmap(composite)
594
595
# Initialize propagators
596
setup_propagators()
597
```
598
599
### Testing Propagation
600
601
```python
602
from opentelemetry import propagate, trace, baggage
603
from opentelemetry.propagators.textmap import DefaultGetter, DefaultSetter
604
605
def test_context_propagation():
606
"""Test context extraction and injection."""
607
tracer = trace.get_tracer(__name__)
608
609
# Create a span and set baggage
610
with tracer.start_as_current_span("test-span") as span:
611
ctx = baggage.set_baggage("test.key", "test.value")
612
token = context.attach(ctx)
613
614
try:
615
# Inject into headers
616
headers = {}
617
propagate.inject(headers)
618
619
print("Injected headers:")
620
for key, value in headers.items():
621
print(f" {key}: {value}")
622
623
# Extract from headers (simulating different service)
624
extracted_context = propagate.extract(headers)
625
626
# Verify extraction
627
token2 = context.attach(extracted_context)
628
try:
629
# Should have baggage and trace context
630
test_value = baggage.get_baggage("test.key")
631
current_span = trace.get_current_span()
632
633
print(f"Extracted baggage: {test_value}")
634
print(f"Extracted span: {current_span.get_span_context()}")
635
636
assert test_value == "test.value"
637
assert current_span.get_span_context().is_valid
638
639
finally:
640
context.detach(token2)
641
finally:
642
context.detach(token)
643
644
# Run test
645
test_context_propagation()
646
```
647
648
## Type Definitions
649
650
```python { .api }
651
from typing import TypeVar, Generic, Dict, List, Optional, Set
652
653
CarrierT = TypeVar("CarrierT") # Type variable for carrier objects
654
```