Skip to content

Commit ca33e7e

Browse files
committed
Merge branch 'potel-base' into potel-base-run-all-tests
2 parents d3d1b60 + 09b4a01 commit ca33e7e

File tree

13 files changed

+92
-62
lines changed

13 files changed

+92
-62
lines changed

MIGRATION_GUIDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
1111
- The SDK now supports Python 3.7 and higher.
1212
- `sentry_sdk.start_span` now only takes keyword arguments.
1313
- `sentry_sdk.start_span` no longer takes an explicit `span` argument.
14+
- `sentry_sdk.start_span` no longer takes explicit `trace_id`, `span_id` or `parent_span_id` arguments.
1415
- The `Span()` constructor does not accept a `hub` parameter anymore.
1516
- `Span.finish()` does not accept a `hub` parameter anymore.
1617
- The `Profile()` constructor does not accept a `hub` parameter anymore.
@@ -19,11 +20,13 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
1920
- Redis integration: In Redis pipeline spans there is no `span["data"]["redis.commands"]` that contains a dict `{"count": 3, "first_ten": ["cmd1", "cmd2", ...]}` but instead `span["data"]["redis.commands.count"]` (containing `3`) and `span["data"]["redis.commands.first_ten"]` (containing `["cmd1", "cmd2", ...]`).
2021
- clickhouse-driver integration: The query is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`).
2122
- `sentry_sdk.init` now returns `None` instead of a context manager.
23+
- The `sampling_context` argument of `traces_sampler` now additionally contains all span attributes known at span start.
2224

2325
### Removed
2426

2527
- Spans no longer have a `description`. Use `name` instead.
2628
- Dropped support for Python 3.6.
29+
- The `custom_sampling_context` parameter of `start_transaction` has been removed. Use `attributes` instead to set key-value pairs of data that should be accessible in the traces sampler. Note that span attributes need to conform to the [OpenTelemetry specification](https://opentelemetry.io/docs/concepts/signals/traces/#attributes), meaning only certain types can be set as values.
2730
- The PyMongo integration no longer sets tags. The data is still accessible via span attributes.
2831
- The PyMongo integration doesn't set `operation_ids` anymore. The individual IDs (`operation_id`, `request_id`, `session_id`) are now accessible as separate span attributes.
2932
- `sentry_sdk.metrics` and associated metrics APIs have been removed as Sentry no longer accepts metrics data in this form. See https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Upcoming-API-Changes-to-Metrics

sentry_sdk/api.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
ExcInfo,
4141
MeasurementUnit,
4242
LogLevelStr,
43-
SamplingContext,
4443
)
4544
from sentry_sdk.tracing import Span, TransactionKwargs
4645

@@ -239,12 +238,8 @@ def flush(
239238
return get_client().flush(timeout=timeout, callback=callback)
240239

241240

242-
def start_span(
243-
*,
244-
custom_sampling_context=None,
245-
**kwargs, # type: Any
246-
):
247-
# type: (...) -> POTelSpan
241+
def start_span(**kwargs):
242+
# type: (type.Any) -> POTelSpan
248243
"""
249244
Start and return a span.
250245
@@ -257,13 +252,11 @@ def start_span(
257252
of the `with` block. If not using context managers, call the `finish()`
258253
method.
259254
"""
260-
# TODO: Consider adding type hints to the method signature.
261-
return get_current_scope().start_span(custom_sampling_context, **kwargs)
255+
return get_current_scope().start_span(**kwargs)
262256

263257

264258
def start_transaction(
265259
transaction=None, # type: Optional[Transaction]
266-
custom_sampling_context=None, # type: Optional[SamplingContext]
267260
**kwargs, # type: Unpack[TransactionKwargs]
268261
):
269262
# type: (...) -> POTelSpan
@@ -295,14 +288,12 @@ def start_transaction(
295288
296289
:param transaction: The transaction to start. If omitted, we create and
297290
start a new transaction.
298-
:param custom_sampling_context: The transaction's custom sampling context.
299291
:param kwargs: Optional keyword arguments to be passed to the Transaction
300292
constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
301293
available arguments.
302294
"""
303295
return start_span(
304296
span=transaction,
305-
custom_sampling_context=custom_sampling_context,
306297
**kwargs,
307298
)
308299

sentry_sdk/integrations/opentelemetry/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ class SentrySpanAttribute:
3030
NAME = "sentry.name"
3131
SOURCE = "sentry.source"
3232
CONTEXT = "sentry.context"
33+
CUSTOM_SAMPLED = "sentry.custom_sampled" # used for saving start_span(sampled=X)

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import cast
33

44
from opentelemetry import trace
5-
65
from opentelemetry.sdk.trace.sampling import Sampler, SamplingResult, Decision
76
from opentelemetry.trace.span import TraceState
87

@@ -12,6 +11,7 @@
1211
from sentry_sdk.integrations.opentelemetry.consts import (
1312
TRACESTATE_SAMPLED_KEY,
1413
TRACESTATE_SAMPLE_RATE_KEY,
14+
SentrySpanAttribute,
1515
)
1616

1717
from typing import TYPE_CHECKING
@@ -114,28 +114,34 @@ def should_sample(
114114

115115
parent_span_context = trace.get_current_span(parent_context).get_span_context()
116116

117+
attributes = attributes or {}
118+
117119
# No tracing enabled, thus no sampling
118120
if not has_tracing_enabled(client.options):
119121
return dropped_result(parent_span_context, attributes)
120122

121-
sample_rate = None
123+
# Explicit sampled value provided at start_span
124+
if attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED) is not None:
125+
sample_rate = float(attributes[SentrySpanAttribute.CUSTOM_SAMPLED])
126+
if sample_rate > 0:
127+
return sampled_result(parent_span_context, attributes, sample_rate)
128+
else:
129+
return dropped_result(parent_span_context, attributes)
122130

123-
# Check if sampled=True was passed to start_transaction
124-
# TODO-anton: Do we want to keep the start_transaction(sampled=True) thing?
131+
sample_rate = None
125132

126133
# Check if there is a traces_sampler
127134
# Traces_sampler is responsible to check parent sampled to have full transactions.
128135
has_traces_sampler = callable(client.options.get("traces_sampler"))
129136
if has_traces_sampler:
130-
# TODO-anton: Make proper sampling_context
131-
# TODO-neel-potel: Make proper sampling_context
132137
sampling_context = {
133138
"transaction_context": {
134139
"name": name,
140+
"op": attributes.get(SentrySpanAttribute.OP),
135141
},
136142
"parent_sampled": get_parent_sampled(parent_span_context, trace_id),
137143
}
138-
144+
sampling_context.update(attributes)
139145
sample_rate = client.options["traces_sampler"](sampling_context)
140146

141147
else:

sentry_sdk/integrations/opentelemetry/scope.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from typing import Tuple, Optional, Generator, Dict, Any
2525
from typing_extensions import Unpack
2626

27-
from sentry_sdk._types import SamplingContext
2827
from sentry_sdk.tracing import TransactionKwargs
2928

3029

@@ -112,22 +111,31 @@ def _incoming_otel_span_context(self):
112111

113112
return span_context
114113

115-
def start_transaction(self, custom_sampling_context=None, **kwargs):
116-
# type: (Optional[SamplingContext], Unpack[TransactionKwargs]) -> POTelSpan
114+
def start_transaction(self, **kwargs):
115+
# type: (Unpack[TransactionKwargs]) -> POTelSpan
117116
"""
118117
.. deprecated:: 3.0.0
119118
This function is deprecated and will be removed in a future release.
120119
Use :py:meth:`sentry_sdk.start_span` instead.
121120
"""
122-
return self.start_span(custom_sampling_context=custom_sampling_context)
121+
return self.start_span(**kwargs)
123122

124-
def start_span(self, custom_sampling_context=None, **kwargs):
125-
# type: (Optional[SamplingContext], Any) -> POTelSpan
123+
def start_span(self, **kwargs):
124+
# type: (Any) -> POTelSpan
126125
return POTelSpan(**kwargs, scope=self)
127126

128127

129-
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
130-
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
128+
_INITIAL_CURRENT_SCOPE = None
129+
_INITIAL_ISOLATION_SCOPE = None
130+
131+
132+
def _setup_initial_scopes():
133+
global _INITIAL_CURRENT_SCOPE, _INITIAL_ISOLATION_SCOPE
134+
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
135+
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
136+
137+
138+
_setup_initial_scopes()
131139

132140

133141
@contextmanager

sentry_sdk/integrations/rq.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,9 @@ def sentry_patched_handle_exception(self, job, *exc_info, **kwargs):
108108
@ensure_integration_enabled(RqIntegration, old_enqueue_job)
109109
def sentry_patched_enqueue_job(self, job, **kwargs):
110110
# type: (Queue, Any, **Any) -> Any
111-
scope = sentry_sdk.get_current_scope()
112-
if scope.span is not None:
113-
job.meta["_sentry_trace_headers"] = dict(
114-
scope.iter_trace_propagation_headers()
115-
)
111+
job.meta["_sentry_trace_headers"] = dict(
112+
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
113+
)
116114

117115
return old_enqueue_job(self, job, **kwargs)
118116

sentry_sdk/scope.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,11 @@ def get_traceparent(self, *args, **kwargs):
497497
client = self.get_client()
498498

499499
# If we have an active span, return traceparent from there
500-
if has_tracing_enabled(client.options) and self.span is not None:
500+
if (
501+
has_tracing_enabled(client.options)
502+
and self.span is not None
503+
and self.span.is_valid
504+
):
501505
return self.span.to_traceparent()
502506

503507
# If this scope has a propagation context, return traceparent from there
@@ -521,7 +525,11 @@ def get_baggage(self, *args, **kwargs):
521525
client = self.get_client()
522526

523527
# If we have an active span, return baggage from there
524-
if has_tracing_enabled(client.options) and self.span is not None:
528+
if (
529+
has_tracing_enabled(client.options)
530+
and self.span is not None
531+
and self.span.is_valid
532+
):
525533
return self.span.to_baggage()
526534

527535
# If this scope has a propagation context, return baggage from there
@@ -610,7 +618,7 @@ def iter_trace_propagation_headers(self, *args, **kwargs):
610618
span = kwargs.pop("span", None)
611619
span = span or self.span
612620

613-
if has_tracing_enabled(client.options) and span is not None:
621+
if has_tracing_enabled(client.options) and span is not None and span.is_valid:
614622
for header in span.iter_headers():
615623
yield header
616624
else:
@@ -946,9 +954,7 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
946954
while len(self._breadcrumbs) > max_breadcrumbs:
947955
self._breadcrumbs.popleft()
948956

949-
def start_transaction(
950-
self, transaction=None, custom_sampling_context=None, **kwargs
951-
):
957+
def start_transaction(self, transaction=None, **kwargs):
952958
# type: (Optional[Transaction], Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan]
953959
"""
954960
Start and return a transaction.
@@ -974,7 +980,6 @@ def start_transaction(
974980
975981
:param transaction: The transaction to start. If omitted, we create and
976982
start a new transaction.
977-
:param custom_sampling_context: The transaction's custom sampling context.
978983
:param kwargs: Optional keyword arguments to be passed to the Transaction
979984
constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
980985
available arguments.
@@ -985,8 +990,6 @@ def start_transaction(
985990

986991
try_autostart_continuous_profiler()
987992

988-
custom_sampling_context = custom_sampling_context or {}
989-
990993
# if we haven't been given a transaction, make one
991994
transaction = Transaction(**kwargs)
992995

@@ -996,7 +999,6 @@ def start_transaction(
996999
"transaction_context": transaction.to_json(),
9971000
"parent_sampled": transaction.parent_sampled,
9981001
}
999-
sampling_context.update(custom_sampling_context)
10001002
transaction._set_initial_sampling_decision(sampling_context=sampling_context)
10011003

10021004
if transaction.sampled:
@@ -1317,7 +1319,11 @@ def _apply_contexts_to_event(self, event, hint, options):
13171319

13181320
# Add "trace" context
13191321
if contexts.get("trace") is None:
1320-
if has_tracing_enabled(options) and self._span is not None:
1322+
if (
1323+
has_tracing_enabled(options)
1324+
and self._span is not None
1325+
and self._span.is_valid
1326+
):
13211327
contexts["trace"] = self._span.get_trace_context()
13221328
else:
13231329
contexts["trace"] = self.get_trace_context()

sentry_sdk/tracing.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343

4444
from typing_extensions import TypedDict, Unpack
4545

46+
from opentelemetry.utils import types as OTelSpanAttributes
47+
4648
P = ParamSpec("P")
4749
R = TypeVar("R")
4850

@@ -1202,10 +1204,12 @@ def __init__(
12021204
op=None, # type: Optional[str]
12031205
description=None, # type: Optional[str]
12041206
status=None, # type: Optional[str]
1207+
sampled=None, # type: Optional[bool]
12051208
start_timestamp=None, # type: Optional[Union[datetime, float]]
12061209
origin=None, # type: Optional[str]
12071210
name=None, # type: Optional[str]
12081211
source=TRANSACTION_SOURCE_CUSTOM, # type: str
1212+
attributes=None, # type: OTelSpanAttributes
12091213
only_if_parent=False, # type: bool
12101214
otel_span=None, # type: Optional[OtelSpan]
12111215
**_, # type: dict[str, object]
@@ -1230,6 +1234,9 @@ def __init__(
12301234
if skip_span:
12311235
self._otel_span = INVALID_SPAN
12321236
else:
1237+
from sentry_sdk.integrations.opentelemetry.consts import (
1238+
SentrySpanAttribute,
1239+
)
12331240
from sentry_sdk.integrations.opentelemetry.utils import (
12341241
convert_to_otel_timestamp,
12351242
)
@@ -1239,12 +1246,18 @@ def __init__(
12391246
start_timestamp = convert_to_otel_timestamp(start_timestamp)
12401247

12411248
span_name = name or description or op or ""
1249+
1250+
# Prepopulate some attrs so that they're accessible in traces_sampler
1251+
attributes = attributes or {}
1252+
attributes[SentrySpanAttribute.OP] = op
1253+
if sampled is not None:
1254+
attributes[SentrySpanAttribute.CUSTOM_SAMPLED] = sampled
1255+
12421256
self._otel_span = tracer.start_span(
1243-
span_name, start_time=start_timestamp
1257+
span_name, start_time=start_timestamp, attributes=attributes
12441258
)
12451259

12461260
self.origin = origin or DEFAULT_SPAN_ORIGIN
1247-
self.op = op
12481261
self.description = description
12491262
self.name = span_name
12501263
self.source = source
@@ -1376,6 +1389,11 @@ def span_id(self):
13761389
# type: () -> str
13771390
return format_span_id(self._otel_span.get_span_context().span_id)
13781391

1392+
@property
1393+
def is_valid(self):
1394+
# type: () -> bool
1395+
return self._otel_span.get_span_context().is_valid
1396+
13791397
@property
13801398
def sampled(self):
13811399
# type: () -> Optional[bool]

tests/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ def clean_scopes():
7474
scope._isolation_scope.set(None)
7575
scope._current_scope.set(None)
7676

77-
potel_scope._INITIAL_CURRENT_SCOPE.clear()
78-
potel_scope._INITIAL_ISOLATION_SCOPE.clear()
77+
potel_scope._setup_initial_scopes()
7978

8079

8180
@pytest.fixture(autouse=True)

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -535,8 +535,6 @@ async def handler(request):
535535
with start_transaction(
536536
name="/interactions/other-dogs/new-dog",
537537
op="greeting.sniff",
538-
# make trace_id difference between transactions
539-
trace_id="0123456789012345678901234567890",
540538
) as transaction:
541539
client = await aiohttp_client(raw_server)
542540
resp = await client.get("/")
@@ -572,14 +570,21 @@ async def handler(request):
572570
with start_transaction(
573571
name="/interactions/other-dogs/new-dog",
574572
op="greeting.sniff",
575-
trace_id="0123456789012345678901234567890",
576-
):
573+
) as transaction:
577574
client = await aiohttp_client(raw_server)
578575
resp = await client.get("/", headers={"bagGage": "custom=value"})
579576

580577
assert (
581-
resp.request_info.headers["baggage"]
582-
== "custom=value,sentry-trace_id=0123456789012345678901234567890,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true"
578+
sorted(resp.request_info.headers["baggage"].split(","))
579+
== sorted([
580+
"custom=value",
581+
f"sentry-trace_id={transaction.trace_id}",
582+
"sentry-environment=production",
583+
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
584+
"sentry-transaction=/interactions/other-dogs/new-dog",
585+
"sentry-sample_rate=1.0",
586+
"sentry-sampled=true",
587+
])
583588
)
584589

585590

0 commit comments

Comments
 (0)