Skip to content

Commit 131f61c

Browse files
committed
safeguards, some type fixes
1 parent c47a0d0 commit 131f61c

File tree

6 files changed

+235
-19
lines changed

6 files changed

+235
-19
lines changed

sentry_sdk/_tracing.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
- {custom_}sampling_context? -> if this is going to die, we need to revive the
4949
potel pr that went through the integrations and got rid of custom_sampling_context
5050
in favor of attributes
51+
- noop spans
52+
- add a switcher to top level API that figures out which @trace to enable
5153
5254
Notes:
5355
- removed ability to provide a start_timestamp
@@ -106,6 +108,10 @@ def __str__(self) -> str:
106108
}
107109

108110

111+
class NoOpStreamedSpan:
112+
pass
113+
114+
109115
class StreamedSpan:
110116
"""
111117
A span holds timing information of a block of code.
@@ -185,7 +191,6 @@ def __init__(
185191

186192
self._sampled: "Optional[bool]" = None
187193
self.sample_rate: "Optional[float]" = None
188-
self._sample_rand: "Optional[float]" = None
189194

190195
# XXX[span-first]: just do this for segments?
191196
self._baggage = baggage
@@ -248,17 +253,17 @@ def end(
248253
self,
249254
end_timestamp: "Optional[Union[float, datetime]]" = None,
250255
scope: "Optional[sentry_sdk.Scope]" = None,
251-
) -> "Optional[str]":
256+
) -> None:
252257
"""
253-
Set the end timestamp of the span.
258+
Set the end timestamp of the span and queue it for sending.
254259
255260
:param end_timestamp: Optional timestamp that should
256261
be used as timestamp instead of the current time.
257262
:param scope: The scope to use.
258263
"""
259264
client = sentry_sdk.get_client()
260265
if not client.is_active():
261-
return None
266+
return
262267

263268
scope: "Optional[sentry_sdk.Scope]" = (
264269
scope or self._scope or sentry_sdk.get_current_scope()
@@ -279,14 +284,14 @@ def end(
279284

280285
client.transport.record_lost_event(reason, data_category="span")
281286

282-
return None
287+
return
283288

284289
if self.sampled is None:
285290
logger.warning("Discarding transaction without sampling decision.")
286291

287292
if self.timestamp is not None:
288293
# This span is already finished, ignore.
289-
return None
294+
return
290295

291296
try:
292297
if end_timestamp:
@@ -304,8 +309,6 @@ def end(
304309
if self.segment.sampled: # XXX this should just use its own sampled
305310
sentry_sdk.get_current_scope()._capture_span(self)
306311

307-
return
308-
309312
def get_attributes(self) -> "Attributes":
310313
return self.attributes
311314

@@ -325,6 +328,10 @@ def get_name(self) -> str:
325328
def set_name(self, name: str) -> None:
326329
self.name = name
327330

331+
def set_flag(self, flag: str, result: bool) -> None:
332+
if len(self._flags) < FLAGS_CAPACITY:
333+
self._flags[flag] = result
334+
328335
def is_segment(self) -> bool:
329336
return self.segment == self
330337

@@ -352,7 +359,7 @@ def sampled(self) -> "Optional[bool]":
352359

353360
return self._sampled
354361

355-
def dynamic_sampling_context(self) -> str:
362+
def dynamic_sampling_context(self) -> dict[str, str]:
356363
return self.segment._get_baggage().dynamic_sampling_context()
357364

358365
def _update_active_thread(self) -> None:

sentry_sdk/api.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sentry_sdk.consts import INSTRUMENTER
88
from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope
99
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
10+
from sentry_sdk._tracing import StreamedSpan
1011
from sentry_sdk.crons import monitor
1112

1213
from typing import TYPE_CHECKING
@@ -385,7 +386,9 @@ def set_measurement(name: str, value: float, unit: "MeasurementUnit" = "") -> No
385386
transaction.set_measurement(name, value, unit)
386387

387388

388-
def get_current_span(scope: "Optional[Scope]" = None) -> "Optional[Span]":
389+
def get_current_span(
390+
scope: "Optional[Scope]" = None,
391+
) -> "Optional[Union[Span, StreamedSpan]]":
389392
"""
390393
Returns the currently active span if there is one running, otherwise `None`
391394
"""
@@ -501,6 +504,16 @@ def update_current_span(
501504
if current_span is None:
502505
return
503506

507+
if isinstance(current_span, StreamedSpan):
508+
warnings.warn(
509+
"The `update_current_span` API isn't available in streaming mode. "
510+
"Retrieve the current span with get_current_span() and use its API "
511+
"directly.",
512+
DeprecationWarning,
513+
stacklevel=2,
514+
)
515+
return
516+
504517
if op is not None:
505518
current_span.op = op
506519

sentry_sdk/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
from sentry_sdk.scope import Scope
7070
from sentry_sdk.session import Session
7171
from sentry_sdk.spotlight import SpotlightClient
72-
from sentry_sdk.trace import StreamedSpan
72+
from sentry_sdk._tracing import StreamedSpan
7373
from sentry_sdk.transport import Transport, Item
7474
from sentry_sdk._log_batcher import LogBatcher
7575
from sentry_sdk._metrics_batcher import MetricsBatcher

sentry_sdk/scope.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,13 @@ def get_traceparent(self, *args: "Any", **kwargs: "Any") -> "Optional[str]":
579579

580580
# If we have an active span, return traceparent from there
581581
if has_tracing_enabled(client.options) and self.span is not None:
582+
if isinstance(self.span, StreamedSpan):
583+
warnings.warn(
584+
"Scope.get_traceparent is not available in streaming mode.",
585+
DeprecationWarning,
586+
stacklevel=2,
587+
)
588+
return None
582589
return self.span.to_traceparent()
583590

584591
# else return traceparent from the propagation context
@@ -593,6 +600,13 @@ def get_baggage(self, *args: "Any", **kwargs: "Any") -> "Optional[Baggage]":
593600

594601
# If we have an active span, return baggage from there
595602
if has_tracing_enabled(client.options) and self.span is not None:
603+
if isinstance(self.span, StreamedSpan):
604+
warnings.warn(
605+
"Scope.get_baggage is not available in streaming mode.",
606+
DeprecationWarning,
607+
stacklevel=2,
608+
)
609+
return None
596610
return self.span.to_baggage()
597611

598612
# else return baggage from the propagation context
@@ -603,6 +617,14 @@ def get_trace_context(self) -> "Dict[str, Any]":
603617
Returns the Sentry "trace" context from the Propagation Context.
604618
"""
605619
if has_tracing_enabled(self.get_client().options) and self._span is not None:
620+
if isinstance(self._span, StreamedSpan):
621+
warnings.warn(
622+
"Scope.get_trace_context is not available in streaming mode.",
623+
DeprecationWarning,
624+
stacklevel=2,
625+
)
626+
return {}
627+
606628
return self._span.get_trace_context()
607629

608630
# if we are tracing externally (otel), those values take precedence
@@ -667,6 +689,15 @@ def iter_trace_propagation_headers(
667689
span = kwargs.pop("span", None)
668690
span = span or self.span
669691

692+
if isinstance(span, StreamedSpan):
693+
warnings.warn(
694+
"Scope.iter_trace_propagation_headers is not available in "
695+
"streaming mode.",
696+
DeprecationWarning,
697+
stacklevel=2,
698+
)
699+
return None
700+
670701
if has_tracing_enabled(client.options) and span is not None:
671702
for header in span.iter_headers():
672703
yield header
@@ -760,6 +791,14 @@ def transaction(self) -> "Any":
760791
if self._span is None:
761792
return None
762793

794+
if isinstance(self._span, StreamedSpan):
795+
warnings.warn(
796+
"Scope.transaction is not available in streaming mode.",
797+
DeprecationWarning,
798+
stacklevel=2,
799+
)
800+
return None
801+
763802
# there is an orphan span on the scope
764803
if self._span.containing_transaction is None:
765804
return None
@@ -789,17 +828,34 @@ def transaction(self, value: "Any") -> None:
789828
"Assigning to scope.transaction directly is deprecated: use scope.set_transaction_name() instead."
790829
)
791830
self._transaction = value
792-
if self._span and self._span.containing_transaction:
793-
self._span.containing_transaction.name = value
831+
if self._span:
832+
if isinstance(self._span, StreamedSpan):
833+
warnings.warn(
834+
"Scope.transaction is not available in streaming mode.",
835+
DeprecationWarning,
836+
stacklevel=2,
837+
)
838+
return None
839+
840+
if self._span.containing_transaction:
841+
self._span.containing_transaction.name = value
794842

795843
def set_transaction_name(self, name: str, source: "Optional[str]" = None) -> None:
796844
"""Set the transaction name and optionally the transaction source."""
797845
self._transaction = name
846+
if self._span:
847+
if isinstance(self._span, StreamedSpan):
848+
warnings.warn(
849+
"Scope.set_transaction_name is not available in streaming mode.",
850+
DeprecationWarning,
851+
stacklevel=2,
852+
)
853+
return None
798854

799-
if self._span and self._span.containing_transaction:
800-
self._span.containing_transaction.name = name
801-
if source:
802-
self._span.containing_transaction.source = source
855+
if self._span.containing_transaction:
856+
self._span.containing_transaction.name = name
857+
if source:
858+
self._span.containing_transaction.source = source
803859

804860
if source:
805861
self._transaction_info["source"] = source
@@ -1116,6 +1172,15 @@ def start_span(
11161172
be removed in the next major version. Going forward, it should only
11171173
be used by the SDK itself.
11181174
"""
1175+
client = sentry_sdk.get_client()
1176+
if has_span_streaming_enabled(client.options):
1177+
warnings.warn(
1178+
"Scope.start_span is not available in streaming mode.",
1179+
DeprecationWarning,
1180+
stacklevel=2,
1181+
)
1182+
return NoOpSpan()
1183+
11191184
if kwargs.get("description") is not None:
11201185
warnings.warn(
11211186
"The `description` parameter is deprecated. Please use `name` instead.",
@@ -1135,6 +1200,9 @@ def start_span(
11351200

11361201
# get current span or transaction
11371202
span = self.span or self.get_isolation_scope().span
1203+
if isinstance(span, StreamedSpan):
1204+
# make mypy happy
1205+
return NoOpSpan()
11381206

11391207
if span is None:
11401208
# New spans get the `trace_id` from the scope

sentry_sdk/tracing.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,69 @@ def calculate_interest_rate(amount, rate, years):
14271427
return decorator
14281428

14291429

1430+
def streamind_trace(
1431+
func: "Optional[Callable[P, R]]" = None,
1432+
*,
1433+
name: "Optional[str]" = None,
1434+
attributes: "Optional[dict[str, Any]]" = None,
1435+
) -> "Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]":
1436+
"""
1437+
Decorator to start a span around a function call.
1438+
1439+
This decorator automatically creates a new span when the decorated function
1440+
is called, and finishes the span when the function returns or raises an exception.
1441+
1442+
:param func: The function to trace. When used as a decorator without parentheses,
1443+
this is the function being decorated. When used with parameters (e.g.,
1444+
``@trace(op="custom")``, this should be None.
1445+
:type func: Callable or None
1446+
1447+
:param name: The human-readable name/description for the span. If not provided,
1448+
defaults to the function name. This provides more specific details about
1449+
what the span represents (e.g., "GET /api/users", "process_user_data").
1450+
:type name: str or None
1451+
1452+
:param attributes: A dictionary of key-value pairs to add as attributes to the span.
1453+
Attribute values must be strings, integers, floats, or booleans. These
1454+
attributes provide additional context about the span's execution.
1455+
:type attributes: dict[str, Any] or None
1456+
1457+
:returns: When used as ``@trace``, returns the decorated function. When used as
1458+
``@trace(...)`` with parameters, returns a decorator function.
1459+
:rtype: Callable or decorator function
1460+
1461+
Example::
1462+
1463+
import sentry_sdk
1464+
1465+
# Simple usage with default values
1466+
@sentry_sdk.trace
1467+
def process_data():
1468+
# Function implementation
1469+
pass
1470+
1471+
# With custom parameters
1472+
@sentry_sdk.trace(
1473+
name="Get user data",
1474+
attributes={"postgres": True}
1475+
)
1476+
def make_db_query(sql):
1477+
# Function implementation
1478+
pass
1479+
"""
1480+
from sentry_sdk.tracing_utils import create_streaming_span_decorator
1481+
1482+
decorator = create_streaming_span_decorator(
1483+
name=name,
1484+
attributes=attributes,
1485+
)
1486+
1487+
if func:
1488+
return decorator(func)
1489+
else:
1490+
return decorator
1491+
1492+
14301493
# Circular imports
14311494

14321495
from sentry_sdk.tracing_utils import (

0 commit comments

Comments
 (0)