Skip to content

Commit 8d2a201

Browse files
committed
Head SDK DSC population
Note that transaction name is missing here for now
1 parent 558daee commit 8d2a201

File tree

5 files changed

+125
-34
lines changed

5 files changed

+125
-34
lines changed

sentry_sdk/integrations/opentelemetry/consts.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from opentelemetry.context import create_key
2+
from sentry_sdk.tracing_utils import Baggage
23

34

45
# propagation keys
@@ -11,7 +12,8 @@
1112
SENTRY_USE_CURRENT_SCOPE_KEY = create_key("sentry_use_current_scope")
1213
SENTRY_USE_ISOLATION_SCOPE_KEY = create_key("sentry_use_isolation_scope")
1314

14-
SENTRY_TRACE_STATE_DROPPED = "sentry_dropped"
15+
TRACESTATE_SAMPLED_KEY = Baggage.SENTRY_PREFIX + "sampled"
16+
TRACESTATE_SAMPLE_RATE_KEY = Baggage.SENTRY_PREFIX + "sample_rate"
1517

1618
OTEL_SENTRY_CONTEXT = "otel"
1719
SPAN_ORIGIN = "auto.otel"

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import cast
12
from random import random
23

34
from opentelemetry import trace
@@ -6,13 +7,17 @@
67
from opentelemetry.trace.span import TraceState
78

89
import sentry_sdk
9-
from sentry_sdk.integrations.opentelemetry.consts import SENTRY_TRACE_STATE_DROPPED
1010
from sentry_sdk.tracing_utils import has_tracing_enabled
1111
from sentry_sdk.utils import is_valid_sample_rate, logger
12+
from sentry_sdk.integrations.opentelemetry.consts import (
13+
TRACESTATE_SAMPLED_KEY,
14+
TRACESTATE_SAMPLE_RATE_KEY,
15+
)
1216

13-
from typing import TYPE_CHECKING, Optional, Sequence
17+
from typing import TYPE_CHECKING
1418

1519
if TYPE_CHECKING:
20+
from typing import Optional, Sequence, Union
1621
from opentelemetry.context import Context
1722
from opentelemetry.trace import Link, SpanKind
1823
from opentelemetry.trace.span import SpanContext
@@ -32,30 +37,42 @@ def get_parent_sampled(parent_context, trace_id):
3237
if parent_context.trace_flags.sampled:
3338
return True
3439

35-
dropped = parent_context.trace_state.get(SENTRY_TRACE_STATE_DROPPED) == "true"
36-
if dropped:
40+
dsc_sampled = parent_context.trace_state.get(TRACESTATE_SAMPLED_KEY)
41+
if dsc_sampled == "true":
42+
return True
43+
elif dsc_sampled == "false":
3744
return False
3845

39-
# TODO-anton: fall back to sampling decision in DSC (for this die DSC needs to be set in the trace_state)
40-
4146
return None
4247

4348

44-
def dropped_result(span_context):
45-
# type: (SpanContext) -> SamplingResult
46-
trace_state = span_context.trace_state.update(SENTRY_TRACE_STATE_DROPPED, "true")
49+
def dropped_result(span_context, attributes, sample_rate=None):
50+
# type: (SpanContext, Attributes, Optional[float]) -> SamplingResult
51+
# note that trace_state.add will NOT overwrite existing entries
52+
# so these will only be added the first time in a root span sampling decision
53+
trace_state = span_context.trace_state.add(TRACESTATE_SAMPLED_KEY, "false")
54+
if sample_rate:
55+
trace_state = trace_state.add(TRACESTATE_SAMPLE_RATE_KEY, str(sample_rate))
4756

4857
return SamplingResult(
4958
Decision.DROP,
59+
attributes=attributes,
5060
trace_state=trace_state,
5161
)
5262

5363

54-
def sampled_result(span_context):
55-
# type: (SpanContext) -> SamplingResult
64+
def sampled_result(span_context, attributes, sample_rate):
65+
# type: (SpanContext, Attributes, float) -> SamplingResult
66+
# note that trace_state.add will NOT overwrite existing entries
67+
# so these will only be added the first time in a root span sampling decision
68+
trace_state = span_context.trace_state.add(TRACESTATE_SAMPLED_KEY, "true").add(
69+
TRACESTATE_SAMPLE_RATE_KEY, str(sample_rate)
70+
)
71+
5672
return SamplingResult(
5773
Decision.RECORD_AND_SAMPLE,
58-
trace_state=span_context.trace_state,
74+
attributes=attributes,
75+
trace_state=trace_state,
5976
)
6077

6178

@@ -77,7 +94,7 @@ def should_sample(
7794

7895
# No tracing enabled, thus no sampling
7996
if not has_tracing_enabled(client.options):
80-
return dropped_result(parent_span_context)
97+
return dropped_result(parent_span_context, attributes)
8198

8299
sample_rate = None
83100

@@ -112,16 +129,16 @@ def should_sample(
112129
logger.warning(
113130
f"[Tracing] Discarding {name} because of invalid sample rate."
114131
)
115-
return dropped_result(parent_span_context)
132+
return dropped_result(parent_span_context, attributes)
116133

117134
# Roll the dice on sample rate
118-
sampled = random() < float(sample_rate)
135+
sample_rate = float(cast("Union[bool, float, int]", sample_rate))
136+
sampled = random() < sample_rate
119137

120-
# TODO-neel-potel set sample rate as attribute for DSC
121138
if sampled:
122-
return sampled_result(parent_span_context)
139+
return sampled_result(parent_span_context, attributes, sample_rate)
123140
else:
124-
return dropped_result(parent_span_context)
141+
return dropped_result(parent_span_context, attributes, sample_rate)
125142

126143
def get_description(self) -> str:
127144
return self.__class__.__name__

sentry_sdk/integrations/opentelemetry/scope.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
SpanContext,
99
NonRecordingSpan,
1010
TraceFlags,
11-
TraceState,
1211
use_span,
1312
)
1413

sentry_sdk/integrations/opentelemetry/utils.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from opentelemetry.semconv.trace import SpanAttributes
1616
from opentelemetry.sdk.trace import ReadableSpan
1717

18+
import sentry_sdk
1819
from sentry_sdk.utils import Dsn
1920
from sentry_sdk.consts import SPANSTATUS
2021
from sentry_sdk.tracing import get_span_status_from_http_code, DEFAULT_SPAN_ORIGIN
@@ -24,7 +25,7 @@
2425
from sentry_sdk._types import TYPE_CHECKING
2526

2627
if TYPE_CHECKING:
27-
from typing import Any, Optional, Mapping, Sequence, Union, ItemsView
28+
from typing import Any, Optional, Mapping, Sequence, Union
2829
from sentry_sdk._types import OtelExtractedSpanData
2930

3031

@@ -300,9 +301,8 @@ def get_trace_context(span, span_data=None):
300301
if span.attributes:
301302
trace_context["data"] = dict(span.attributes)
302303

303-
trace_context["dynamic_sampling_context"] = dsc_from_trace_state(
304-
span.context.trace_state
305-
)
304+
trace_state = get_trace_state(span)
305+
trace_context["dynamic_sampling_context"] = dsc_from_trace_state(trace_state)
306306

307307
# TODO-neel-potel profiler thread_id, thread_name
308308

@@ -319,6 +319,11 @@ def trace_state_from_baggage(baggage):
319319
return TraceState(items)
320320

321321

322+
def baggage_from_trace_state(trace_state):
323+
# type: (TraceState) -> Baggage
324+
return Baggage(dsc_from_trace_state(trace_state))
325+
326+
322327
def serialize_trace_state(trace_state):
323328
# type: (TraceState) -> str
324329
sentry_items = []
@@ -336,3 +341,55 @@ def dsc_from_trace_state(trace_state):
336341
key = re.sub(Baggage.SENTRY_PREFIX_REGEX, "", k)
337342
dsc[key] = v
338343
return dsc
344+
345+
346+
def has_incoming_trace(trace_state):
347+
# type: (TraceState) -> bool
348+
"""
349+
The existence a sentry-trace_id in the baggage implies we continued an upstream trace.
350+
"""
351+
return (Baggage.SENTRY_PREFIX + "trace_id") in trace_state
352+
353+
354+
def get_trace_state(span):
355+
# type: (Union[Span, ReadableSpan]) -> TraceState
356+
"""
357+
Get the existing trace_state with sentry items
358+
or populate it if we are the head SDK.
359+
"""
360+
span_context = span.get_span_context()
361+
if not span_context:
362+
return TraceState()
363+
364+
trace_state = span_context.trace_state
365+
366+
if has_incoming_trace(trace_state):
367+
return trace_state
368+
else:
369+
client = sentry_sdk.get_client()
370+
if not client.is_active():
371+
return trace_state
372+
373+
options = client.options or {}
374+
375+
trace_state = trace_state.update(
376+
Baggage.SENTRY_PREFIX + "trace_id", format_trace_id(span_context.trace_id)
377+
)
378+
379+
if options.get("environment"):
380+
trace_state = trace_state.update(
381+
Baggage.SENTRY_PREFIX + "environment", options["environment"]
382+
)
383+
384+
if options.get("release"):
385+
trace_state = trace_state.update(
386+
Baggage.SENTRY_PREFIX + "release", options["release"]
387+
)
388+
389+
if options.get("dsn"):
390+
trace_state = trace_state.update(
391+
Baggage.SENTRY_PREFIX + "public_key", Dsn(options["dsn"]).public_key
392+
)
393+
394+
# TODO-neel-potel head dsc transaction name
395+
return trace_state

sentry_sdk/tracing.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
from datetime import datetime, timedelta, timezone
66

77
from opentelemetry import trace as otel_trace, context
8-
from opentelemetry.trace import format_trace_id, format_span_id, Span as OtelSpan
8+
from opentelemetry.trace import (
9+
format_trace_id,
10+
format_span_id,
11+
Span as OtelSpan,
12+
TraceState,
13+
)
914
from opentelemetry.trace.status import StatusCode
1015
from opentelemetry.sdk.trace import ReadableSpan
1116

@@ -1453,8 +1458,7 @@ def iter_headers(self):
14531458
serialize_trace_state,
14541459
)
14551460

1456-
trace_state = self._otel_span.get_span_context().trace_state
1457-
yield BAGGAGE_HEADER_NAME, serialize_trace_state(trace_state)
1461+
yield BAGGAGE_HEADER_NAME, serialize_trace_state(self.trace_state)
14581462

14591463
def to_traceparent(self):
14601464
# type: () -> str
@@ -1471,10 +1475,26 @@ def to_traceparent(self):
14711475

14721476
return traceparent
14731477

1478+
@property
1479+
def trace_state(self):
1480+
# type: () -> TraceState
1481+
from sentry_sdk.integrations.opentelemetry.utils import (
1482+
get_trace_state,
1483+
)
1484+
1485+
return get_trace_state(self._otel_span)
1486+
14741487
def to_baggage(self):
1475-
# type: () -> Optional[Baggage]
1476-
# TODO-neel-potel head SDK populate baggage mess
1477-
pass
1488+
# type: () -> Baggage
1489+
return self.get_baggage()
1490+
1491+
def get_baggage(self):
1492+
# type: () -> Baggage
1493+
from sentry_sdk.integrations.opentelemetry.utils import (
1494+
baggage_from_trace_state,
1495+
)
1496+
1497+
return baggage_from_trace_state(self.trace_state)
14781498

14791499
def set_tag(self, key, value):
14801500
# type: (str, Any) -> None
@@ -1568,10 +1588,6 @@ def set_context(self, key, value):
15681588
# type: (str, Any) -> None
15691589
pass
15701590

1571-
def get_baggage(self):
1572-
# type: () -> Baggage
1573-
pass
1574-
15751591

15761592
if TYPE_CHECKING:
15771593

0 commit comments

Comments
 (0)