Skip to content

Commit b01caab

Browse files
committed
.
1 parent 0d2097b commit b01caab

File tree

5 files changed

+202
-120
lines changed

5 files changed

+202
-120
lines changed

sentry_sdk/_span_batcher.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry_sdk._batcher import Batcher
77
from sentry_sdk.consts import SPANSTATUS
88
from sentry_sdk.envelope import Envelope, Item, PayloadRef
9-
from sentry_sdk.utils import serialize_attribute, safe_repr
9+
from sentry_sdk.utils import format_timestamp, serialize_attribute, safe_repr
1010

1111
if TYPE_CHECKING:
1212
from typing import Any, Callable, Optional
@@ -71,24 +71,24 @@ def _to_transport_format(item: "StreamedSpan") -> "Any":
7171
"trace_id": item.trace_id,
7272
"span_id": item.span_id,
7373
"name": item.get_name(),
74-
"status": item._status,
74+
"status": item.status.value,
7575
"is_segment": item.is_segment(),
76-
"start_timestamp": item._start_timestamp.timestamp(), # TODO[span-first]
77-
"end_timestamp": item._timestamp.timestamp(),
76+
"start_timestamp": item.start_timestamp.timestamp(), # TODO[span-first]
77+
"end_timestamp": item.timestamp.timestamp(),
7878
}
7979

80-
if item._parent_span_id:
81-
res["parent_span_id"] = item._parent_span_id
80+
if item.parent_span_id:
81+
res["parent_span_id"] = item.parent_span_id
8282

83-
if item._attributes:
83+
if item.attributes:
8484
res["attributes"] = {
85-
k: serialize_attribute(v) for (k, v) in item._attributes.items()
85+
k: serialize_attribute(v) for (k, v) in item.attributes.items()
8686
}
8787

8888
return res
8989

90-
def _flush(self) -> Optional[Envelope]:
91-
from sentry_sdk.utils import format_timestamp
90+
def _flush(self) -> "Optional[Envelope]":
91+
print("batcher.flush")
9292

9393
with self._lock:
9494
if len(self._span_buffer) == 0:

sentry_sdk/_tracing.py

Lines changed: 93 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sentry_sdk.profiler.continuous_profiler import get_profiler_id
99
from sentry_sdk.tracing_utils import (
1010
Baggage,
11+
_generate_sample_rand,
1112
has_span_streaming_enabled,
1213
has_tracing_enabled,
1314
)
@@ -38,10 +39,15 @@
3839
- initial status: OK? or unset?
3940
- dropped spans are not migrated
4041
- recheck transaction.finish <-> Streamedspan.end
41-
- profile not part of the event, how to send?
42+
- profiling: drop transaction based
43+
- profiling: actually send profiles
4244
- maybe: use getters/setter OR properties but not both
4345
- add size-based flushing to buffer(s)
4446
- migrate transaction sample_rand logic
47+
- remove deprecated profiler impl
48+
- {custom_}sampling_context? -> if this is going to die, we need to revive the
49+
potel pr that went through the integrations and got rid of custom_sampling_context
50+
in favor of attributes
4551
4652
Notes:
4753
- removed ability to provide a start_timestamp
@@ -54,7 +60,9 @@ def start_span(
5460
attributes: "Optional[Attributes]" = None,
5561
parent_span: "Optional[StreamedSpan]" = None,
5662
) -> "StreamedSpan":
57-
return sentry_sdk.get_current_scope().start_streamed_span()
63+
return sentry_sdk.get_current_scope().start_streamed_span(
64+
name, attributes, parent_span
65+
)
5866

5967

6068
class SpanStatus(str, Enum):
@@ -109,44 +117,57 @@ class StreamedSpan:
109117
"""
110118

111119
__slots__ = (
112-
"_name",
113-
"_attributes",
120+
"name",
121+
"attributes",
114122
"_span_id",
115123
"_trace_id",
116-
"_parent_span_id",
117-
"_segment",
124+
"parent_span_id",
125+
"segment",
118126
"_sampled",
119-
"_start_timestamp",
120-
"_timestamp",
121-
"_status",
127+
"parent_sampled",
128+
"start_timestamp",
129+
"timestamp",
130+
"status",
122131
"_start_timestamp_monotonic_ns",
123132
"_scope",
124133
"_flags",
125134
"_context_manager_state",
126135
"_profile",
127136
"_continuous_profile",
128137
"_baggage",
129-
"_sample_rate",
138+
"sample_rate",
130139
"_sample_rand",
140+
"source",
131141
)
132142

133143
def __init__(
134144
self,
145+
*,
135146
name: str,
136-
trace_id: str,
147+
scope: "Scope",
137148
attributes: "Optional[Attributes]" = None,
149+
# TODO[span-first]: would be good to actually take this propagation
150+
# context stuff directly from the PropagationContext, but for that
151+
# we'd actually need to refactor PropagationContext to stay in sync
152+
# with what's going on (e.g. update the current span_id) and not just
153+
# update when a trace is continued
154+
trace_id: "Optional[str]" = None,
138155
parent_span_id: "Optional[str]" = None,
156+
parent_sampled: "Optional[bool]" = None,
157+
baggage: "Optional[Baggage]" = None,
139158
segment: "Optional[StreamedSpan]" = None,
140-
scope: "Optional[Scope]" = None,
141159
) -> None:
142-
self._name: str = name
143-
self._attributes: "Attributes" = attributes
160+
self._scope = scope
161+
162+
self.name: str = name
163+
self.attributes: "Attributes" = attributes
144164

145165
self._trace_id = trace_id
146-
self._parent_span_id = parent_span_id
147-
self._segment = segment or self
166+
self.parent_span_id = parent_span_id
167+
self.parent_sampled = parent_sampled
168+
self.segment = segment or self
148169

149-
self._start_timestamp = datetime.now(timezone.utc)
170+
self.start_timestamp = datetime.now(timezone.utc)
150171

151172
try:
152173
# profiling depends on this value and requires that
@@ -155,24 +176,42 @@ def __init__(
155176
except AttributeError:
156177
pass
157178

158-
self._timestamp: "Optional[datetime]" = None
179+
self.timestamp: "Optional[datetime]" = None
159180
self._span_id: "Optional[str]" = None
160-
self._status: SpanStatus = SpanStatus.OK
181+
182+
self.status: SpanStatus = SpanStatus.OK
183+
self.source: "Optional[SegmentSource]" = SegmentSource.CUSTOM
184+
# XXX[span-first] ^ populate this correctly
185+
161186
self._sampled: "Optional[bool]" = None
162-
self._scope: "Optional[Scope]" = scope # TODO[span-first] when are we starting a span with a specific scope? is this needed?
187+
self.sample_rate: "Optional[float]" = None
188+
self._sample_rand: "Optional[float]" = None
189+
190+
# XXX[span-first]: just do this for segments?
191+
self._baggage = baggage
192+
baggage_sample_rand = (
193+
None if self._baggage is None else self._baggage._sample_rand()
194+
)
195+
if baggage_sample_rand is not None:
196+
self._sample_rand = baggage_sample_rand
197+
else:
198+
self._sample_rand = _generate_sample_rand(self.trace_id)
199+
163200
self._flags: dict[str, bool] = {}
201+
self._profile = None
202+
self._continuous_profile = None
164203

165204
self._update_active_thread()
166205
self._set_profiler_id(get_profiler_id())
167206

168207
def __repr__(self) -> str:
169208
return (
170209
f"<{self.__class__.__name__}("
171-
f"name={self._name}, "
172-
f"trace_id={self._trace_id}, "
173-
f"span_id={self._span_id}, "
174-
f"parent_span_id={self._parent_span_id}, "
175-
f"sampled={self._sampled})>"
210+
f"name={self.name}, "
211+
f"trace_id={self.trace_id}, "
212+
f"span_id={self.span_id}, "
213+
f"parent_span_id={self.parent_span_id}, "
214+
f"sampled={self.sampled})>"
176215
)
177216

178217
def __enter__(self) -> "StreamedSpan":
@@ -215,8 +254,7 @@ def end(
215254
216255
:param end_timestamp: Optional timestamp that should
217256
be used as timestamp instead of the current time.
218-
:param scope: The scope to use for this transaction.
219-
If not provided, the current scope will be used.
257+
:param scope: The scope to use.
220258
"""
221259
client = sentry_sdk.get_client()
222260
if not client.is_active():
@@ -227,7 +265,7 @@ def end(
227265
)
228266

229267
# Explicit check against False needed because self.sampled might be None
230-
if self._sampled is False:
268+
if self.sampled is False:
231269
logger.debug("Discarding span because sampled = False")
232270

233271
# This is not entirely accurate because discards here are not
@@ -243,7 +281,7 @@ def end(
243281

244282
return None
245283

246-
if self._sampled is None:
284+
if self.sampled is None:
247285
logger.warning("Discarding transaction without sampling decision.")
248286

249287
if self.timestamp is not None:
@@ -257,46 +295,39 @@ def end(
257295
self.timestamp = end_timestamp
258296
else:
259297
elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns
260-
self.timestamp = self._start_timestamp + timedelta(
298+
self.timestamp = self.start_timestamp + timedelta(
261299
microseconds=elapsed / 1000
262300
)
263301
except AttributeError:
264302
self.timestamp = datetime.now(timezone.utc)
265303

266-
if self.segment.sampled:
267-
client._capture_span(self)
304+
if self.segment.sampled: # XXX this should just use its own sampled
305+
sentry_sdk.get_current_scope()._capture_span(self)
306+
268307
return
269308

270309
def get_attributes(self) -> "Attributes":
271-
return self._attributes
310+
return self.attributes
272311

273312
def set_attribute(self, key: str, value: "AttributeValue") -> None:
274-
self._attributes[key] = format_attribute(value)
313+
self.attributes[key] = format_attribute(value)
275314

276315
def set_attributes(self, attributes: "Attributes") -> None:
277316
for key, value in attributes.items():
278317
self.set_attribute(key, value)
279318

280319
def set_status(self, status: SpanStatus) -> None:
281-
self._status = status
320+
self.status = status
282321

283322
def get_name(self) -> str:
284-
return self._name
323+
return self.name
285324

286325
def set_name(self, name: str) -> None:
287-
self._name = name
288-
289-
@property
290-
def segment(self) -> "StreamedSpan":
291-
return self._segment
326+
self.name = name
292327

293328
def is_segment(self) -> bool:
294329
return self.segment == self
295330

296-
@property
297-
def sampled(self) -> "Optional[bool]":
298-
return self._sampled
299-
300331
@property
301332
def span_id(self) -> str:
302333
if not self._span_id:
@@ -312,6 +343,15 @@ def trace_id(self) -> str:
312343
return self._trace_id
313344

314345
@property
346+
def sampled(self) -> "Optional[bool]":
347+
if self._sampled is not None:
348+
return self._sampled
349+
350+
if not self.is_segment():
351+
self._sampled = self.parent_sampled
352+
353+
return self._sampled
354+
315355
def dynamic_sampling_context(self) -> str:
316356
return self.segment._get_baggage().dynamic_sampling_context()
317357

@@ -352,29 +392,15 @@ def _get_baggage(self) -> "Baggage":
352392

353393
return self._baggage
354394

355-
def _set_initial_sampling_decision(
356-
self, sampling_context: "SamplingContext"
357-
) -> None:
395+
def _set_sampling_decision(self, sampling_context: "SamplingContext") -> None:
358396
"""
359-
Sets the segment's sampling decision, according to the following
360-
precedence rules:
361-
362-
1. If `traces_sampler` is defined, its decision will be used. It can
363-
choose to keep or ignore any parent sampling decision, or use the
364-
sampling context data to make its own decision or to choose a sample
365-
rate for the transaction.
366-
367-
2. If `traces_sampler` is not defined, but there's a parent sampling
368-
decision, the parent sampling decision will be used.
369-
370-
3. If `traces_sampler` is not defined and there's no parent sampling
371-
decision, `traces_sample_rate` will be used.
397+
Set the segment's sampling decision, inherited by all child spans.
372398
"""
373399
client = sentry_sdk.get_client()
374400

375401
# nothing to do if tracing is disabled
376402
if not has_tracing_enabled(client.options):
377-
self.sampled = False
403+
self._sampled = False
378404
return
379405

380406
if not self.is_segment():
@@ -398,9 +424,9 @@ def _set_initial_sampling_decision(
398424
# booleans or numbers between 0 and 1.)
399425
if not is_valid_sample_rate(sample_rate, source="Tracing"):
400426
logger.warning(
401-
f"[Tracing] Discarding {self._name} because of invalid sample rate."
427+
f"[Tracing] Discarding {self.name} because of invalid sample rate."
402428
)
403-
self.sampled = False
429+
self._sampled = False
404430
return
405431

406432
self.sample_rate = float(sample_rate)
@@ -416,12 +442,12 @@ def _set_initial_sampling_decision(
416442
else:
417443
reason = "traces_sample_rate is set to 0"
418444

419-
logger.debug(f"[Tracing] Discarding {self._name} because {reason}")
420-
self.sampled = False
445+
logger.debug(f"[Tracing] Discarding {self.name} because {reason}")
446+
self._sampled = False
421447
return
422448

423449
# Now we roll the dice.
424-
self.sampled = self._sample_rand < self.sample_rate
450+
self._sampled = self._sample_rand < self.sample_rate
425451

426452
if self.sampled:
427453
logger.debug(f"[Tracing] Starting {self.name}")

0 commit comments

Comments
 (0)