Skip to content

Commit 93c83e2

Browse files
committed
.
1 parent 186718e commit 93c83e2

File tree

6 files changed

+91
-8
lines changed

6 files changed

+91
-8
lines changed

sentry_sdk/_types.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from consts import SPANSTATUS
12
from typing import TYPE_CHECKING, TypeVar, Union
23

34

@@ -222,6 +223,17 @@ class SDKInfo(TypedDict):
222223
# TODO: Make a proper type definition for this (PRs welcome!)
223224
Hint = Dict[str, Any]
224225

226+
# TODO: Consolidate attribute types. E.g. metrics use the same attr structure.
227+
# I'm keeping span attrs separate for now so that the span first stuff is
228+
# isolated until it's not experimental.
229+
SpanAttributeValue = TypedDict(
230+
"SpanAttributeValue",
231+
{
232+
"type": Literal["string", "boolean", "double", "integer"],
233+
"value": str | bool | float | int,
234+
},
235+
)
236+
225237
Log = TypedDict(
226238
"Log",
227239
{
@@ -260,6 +272,23 @@ class SDKInfo(TypedDict):
260272

261273
MetricProcessor = Callable[[Metric, Hint], Optional[Metric]]
262274

275+
# This is the V2 span format
276+
# https://develop.sentry.dev/sdk/telemetry/spans/span-protocol/
277+
SpanV2 = TypedDict(
278+
"SpanV2",
279+
{
280+
"trace_id": str,
281+
"span_id": str,
282+
"parent_span_id": Optional[str],
283+
"name": str,
284+
"status": Literal[SPANSTATUS.OK, SPANSTATUS.ERROR],
285+
"is_segment": bool,
286+
"start_timestamp": float,
287+
"end_timestamp": float,
288+
"attributes": Optional[dict[str, SpanAttributeValue]],
289+
},
290+
)
291+
263292
# TODO: Make a proper type definition for this (PRs welcome!)
264293
Breadcrumb = Dict[str, Any]
265294

sentry_sdk/client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import sentry_sdk
1212
from sentry_sdk._compat import PY37, check_uwsgi_thread_support
1313
from sentry_sdk._metrics_batcher import MetricsBatcher
14+
from sentry_sdk._span_batcher import SpanBatcher
1415
from sentry_sdk.utils import (
1516
AnnotatedValue,
1617
ContextVar,
@@ -187,6 +188,7 @@ def __init__(self, options=None):
187188
self.monitor = None # type: Optional[Monitor]
188189
self.log_batcher = None # type: Optional[LogBatcher]
189190
self.metrics_batcher = None # type: Optional[MetricsBatcher]
191+
self._span_batcher = None # type: Optional[SpanBatcher]
190192

191193
def __getstate__(self, *args, **kwargs):
192194
# type: (*Any, **Any) -> Any
@@ -398,6 +400,13 @@ def _record_lost_event(
398400
record_lost_func=_record_lost_event,
399401
)
400402

403+
self._span_batcher = None
404+
if self.options["_experiments"].get("trace_lifecycle", None) == "stream":
405+
self._span_batcher = SpanBatcher(
406+
capture_func=_capture_envelope,
407+
record_lost_func=_record_lost_event,
408+
)
409+
401410
max_request_body_size = ("always", "never", "small", "medium")
402411
if self.options["max_request_body_size"] not in max_request_body_size:
403412
raise ValueError(

sentry_sdk/scope.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sentry_sdk.session import Session
2323
from sentry_sdk.tracing_utils import (
2424
Baggage,
25+
has_streaming_enabled,
2526
has_tracing_enabled,
2627
normalize_incoming_data,
2728
PropagationContext,
@@ -1124,9 +1125,10 @@ def start_transaction(
11241125
transaction.set_profiler_id(get_profiler_id())
11251126

11261127
# we don't bother to keep spans if we already know we're not going to
1127-
# send the transaction
1128-
max_spans = (client.options["_experiments"].get("max_spans")) or 1000
1129-
transaction.init_span_recorder(maxlen=max_spans)
1128+
# send the transaction or if we're in streaming mode
1129+
if not has_streaming_enabled(client.options):
1130+
max_spans = (client.options["_experiments"].get("max_spans")) or 1000
1131+
transaction.init_span_recorder(maxlen=max_spans)
11301132

11311133
return transaction
11321134

sentry_sdk/tracing.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,14 @@ def finish(self, scope=None, end_timestamp=None):
711711
scope = scope or sentry_sdk.get_current_scope()
712712
maybe_create_breadcrumbs_from_span(scope, self)
713713

714+
client = sentry_sdk.get_client()
715+
if client.is_active():
716+
if (
717+
has_span_streaming_enabled(client.options)
718+
and self.containing_transaction.sampled
719+
):
720+
client._span_batcher.add(self)
721+
714722
return None
715723

716724
def to_json(self):
@@ -857,6 +865,12 @@ def __init__( # type: ignore[misc]
857865
else:
858866
self._sample_rand = _generate_sample_rand(self.trace_id)
859867

868+
self._mode = "static"
869+
client = sentry_sdk.get_client()
870+
if client.is_active():
871+
if has_span_streaming_enabled(client.options):
872+
self._mode = "stream"
873+
860874
def __repr__(self):
861875
# type: () -> str
862876
return (
@@ -882,9 +896,11 @@ def _possibly_started(self):
882896
with sentry_sdk.start_transaction, and therefore the transaction will
883897
be discarded.
884898
"""
885-
886-
# We must explicitly check self.sampled is False since self.sampled can be None
887-
return self._span_recorder is not None or self.sampled is False
899+
if self._mode == "static":
900+
# We must explicitly check self.sampled is False since self.sampled can be None
901+
return self._span_recorder is not None or self.sampled is False
902+
else:
903+
return True
888904

889905
def __enter__(self):
890906
# type: () -> Transaction
@@ -972,7 +988,8 @@ def finish(
972988
):
973989
# type: (...) -> Optional[str]
974990
"""Finishes the transaction and sends it to Sentry.
975-
All finished spans in the transaction will also be sent to Sentry.
991+
If we're in non-streaming mode, all finished spans in the transaction
992+
will also be sent to Sentry at this point.
976993
977994
:param scope: The Scope to use for this transaction.
978995
If not provided, the current Scope will be used.
@@ -1000,7 +1017,7 @@ def finish(
10001017
# We have no active client and therefore nowhere to send this transaction.
10011018
return None
10021019

1003-
if self._span_recorder is None:
1020+
if self._mode == "static" and self._span_recorder is None:
10041021
# Explicit check against False needed because self.sampled might be None
10051022
if self.sampled is False:
10061023
logger.debug("Discarding transaction because sampled = False")
@@ -1482,5 +1499,6 @@ def calculate_interest_rate(amount, rate, years):
14821499
extract_sentrytrace_data,
14831500
_generate_sample_rand,
14841501
has_tracing_enabled,
1502+
has_span_streaming_enabled,
14851503
maybe_create_breadcrumbs_from_span,
14861504
)

sentry_sdk/tracing_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ def has_tracing_enabled(options):
110110
)
111111

112112

113+
def has_span_streaming_enabled(options):
114+
# type: (Optional[Dict[str, Any]]) -> bool
115+
if options is None:
116+
return False
117+
118+
return options.get("_experiments").get("trace_lifecycle") == "stream"
119+
120+
113121
@contextlib.contextmanager
114122
def record_sql_queries(
115123
cursor, # type: Any

sentry_sdk/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,3 +2076,20 @@ def get_before_send_metric(options):
20762076
return options.get("before_send_metric") or options["_experiments"].get(
20772077
"before_send_metric"
20782078
)
2079+
2080+
2081+
def format_attribute_value(value):
2082+
# type: (Any) -> dict[str, bool | str | int | float]
2083+
if isinstance(value, bool):
2084+
return {"value": value, "type": "boolean"}
2085+
2086+
if isinstance(value, int):
2087+
return {"value": value, "type": "integer"}
2088+
2089+
if isinstance(value, float):
2090+
return {"value": value, "type": "double"}
2091+
2092+
if isinstance(value, str):
2093+
return {"value": value, "type": "string"}
2094+
2095+
return {"value": safe_repr(value), "type": "string"}

0 commit comments

Comments
 (0)