/
propagator.py
117 lines (93 loc) · 3.65 KB
/
propagator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from opentelemetry import trace # type: ignore
from opentelemetry.context import ( # type: ignore
Context,
create_key,
get_current,
set_value,
)
from opentelemetry.propagators.textmap import ( # type: ignore
CarrierT,
Getter,
Setter,
TextMapPropagator,
default_getter,
default_setter,
)
from opentelemetry.trace import ( # type: ignore
TraceFlags,
NonRecordingSpan,
SpanContext,
)
from sentry_sdk.tracing import (
BAGGAGE_HEADER_NAME,
SENTRY_TRACE_HEADER_NAME,
)
from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
from sentry_sdk._types import MYPY
if MYPY:
from typing import Optional
from typing import Set
SENTRY_TRACE_KEY = create_key("sentry-trace")
SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")
class SentryPropagator(TextMapPropagator): # type: ignore
"""
Propagates tracing headers for Sentry's tracing system in a way OTel understands.
"""
def extract(self, carrier, context=None, getter=default_getter):
# type: (CarrierT, Optional[Context], Getter) -> Context
if context is None:
context = get_current()
sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
if not sentry_trace:
return context
sentrytrace = extract_sentrytrace_data(sentry_trace[0])
if not sentrytrace:
return context
sentry_trace_data = (
sentrytrace.get("trace_id", "0"),
sentrytrace.get("parent_span_id", "0"),
sentrytrace.get("parent_sampled", None),
)
context = set_value(SENTRY_TRACE_KEY, sentry_trace_data, context)
trace_id, span_id, _ = sentry_trace_data
span_context = SpanContext(
trace_id=int(trace_id, 16), # type: ignore
span_id=int(span_id, 16), # type: ignore
# we simulate a sampled trace on the otel side and leave the sampling to sentry
trace_flags=TraceFlags(TraceFlags.SAMPLED),
is_remote=True,
)
baggage_header = getter.get(carrier, BAGGAGE_HEADER_NAME)
if baggage_header:
baggage = Baggage.from_incoming_header(baggage_header[0])
else:
# If there's an incoming sentry-trace but no incoming baggage header,
# for instance in traces coming from older SDKs,
# baggage will be empty and frozen and won't be populated as head SDK.
baggage = Baggage(sentry_items={})
baggage.freeze()
context = set_value(SENTRY_BAGGAGE_KEY, baggage, context)
span = NonRecordingSpan(span_context)
modified_context = trace.set_span_in_context(span, context)
return modified_context
def inject(self, carrier, context=None, setter=default_setter):
# type: (CarrierT, Optional[Context], Setter) -> None
if context is None:
context = get_current()
current_span = trace.get_current_span(context)
span_id = trace.format_span_id(current_span.context.span_id)
from sentry_sdk.integrations.opentelemetry.span_processor import (
SentrySpanProcessor,
)
span_map = SentrySpanProcessor().otel_span_map
sentry_span = span_map.get(span_id, None)
if not sentry_span:
return
setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent())
baggage = hasattr(sentry_span, "get_baggage") and sentry_span.get_baggage()
if baggage:
setter.set(carrier, BAGGAGE_HEADER_NAME, baggage.serialize())
@property
def fields(self):
# type: () -> Set[str]
return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME}