Skip to content

Commit fe7e501

Browse files
authored
Tracing without performance (#2136)
Tracing information (sentry-trace and baggage headers) is now propagated from/to incoming/outgoing HTTP requests even if performance is disabled and thus no transactions/spans are available.
1 parent e833825 commit fe7e501

File tree

31 files changed

+1748
-145
lines changed

31 files changed

+1748
-145
lines changed

sentry_sdk/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"set_level",
3737
"set_measurement",
3838
"get_current_span",
39+
"get_traceparent",
40+
"get_baggage",
41+
"continue_trace",
3942
]
4043

4144
# Initialize the debug support after everything is loaded

sentry_sdk/api.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import inspect
22

3+
from sentry_sdk._types import TYPE_CHECKING
34
from sentry_sdk.hub import Hub
45
from sentry_sdk.scope import Scope
5-
6-
from sentry_sdk._types import TYPE_CHECKING
7-
from sentry_sdk.tracing import NoOpSpan
6+
from sentry_sdk.tracing import NoOpSpan, Transaction
7+
from sentry_sdk.tracing_utils import (
8+
has_tracing_enabled,
9+
normalize_incoming_data,
10+
)
811

912
if TYPE_CHECKING:
1013
from typing import Any
@@ -24,7 +27,7 @@
2427
ExcInfo,
2528
MeasurementUnit,
2629
)
27-
from sentry_sdk.tracing import Span, Transaction
30+
from sentry_sdk.tracing import Span
2831

2932
T = TypeVar("T")
3033
F = TypeVar("F", bound=Callable[..., Any])
@@ -54,6 +57,9 @@ def overload(x):
5457
"set_level",
5558
"set_measurement",
5659
"get_current_span",
60+
"get_traceparent",
61+
"get_baggage",
62+
"continue_trace",
5763
]
5864

5965

@@ -241,3 +247,54 @@ def get_current_span(hub=None):
241247

242248
current_span = hub.scope.span
243249
return current_span
250+
251+
252+
def get_traceparent():
253+
# type: () -> Optional[str]
254+
"""
255+
Returns the traceparent either from the active span or from the scope.
256+
"""
257+
hub = Hub.current
258+
if hub.client is not None:
259+
if has_tracing_enabled(hub.client.options) and hub.scope.span is not None:
260+
return hub.scope.span.to_traceparent()
261+
262+
return hub.scope.get_traceparent()
263+
264+
265+
def get_baggage():
266+
# type: () -> Optional[str]
267+
"""
268+
Returns Baggage either from the active span or from the scope.
269+
"""
270+
hub = Hub.current
271+
if (
272+
hub.client is not None
273+
and has_tracing_enabled(hub.client.options)
274+
and hub.scope.span is not None
275+
):
276+
baggage = hub.scope.span.to_baggage()
277+
else:
278+
baggage = hub.scope.get_baggage()
279+
280+
if baggage is not None:
281+
return baggage.serialize()
282+
283+
return None
284+
285+
286+
def continue_trace(environ_or_headers, op=None, name=None, source=None):
287+
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction
288+
"""
289+
Sets the propagation context from environment or headers and returns a transaction.
290+
"""
291+
with Hub.current.configure_scope() as scope:
292+
scope.generate_propagation_context(environ_or_headers)
293+
294+
transaction = Transaction.continue_from_headers(
295+
normalize_incoming_data(environ_or_headers),
296+
op=op,
297+
name=name,
298+
source=source,
299+
)
300+
return transaction

sentry_sdk/client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def _prepare_event(
262262

263263
if scope is not None:
264264
is_transaction = event.get("type") == "transaction"
265-
event_ = scope.apply_to_event(event, hint)
265+
event_ = scope.apply_to_event(event, hint, self.options)
266266

267267
# one of the event/error processors returned None
268268
if event_ is None:
@@ -507,11 +507,8 @@ def capture_event(
507507
is_checkin = event_opt.get("type") == "check_in"
508508
attachments = hint.get("attachments")
509509

510-
dynamic_sampling_context = (
511-
event_opt.get("contexts", {})
512-
.get("trace", {})
513-
.pop("dynamic_sampling_context", {})
514-
)
510+
trace_context = event_opt.get("contexts", {}).get("trace") or {}
511+
dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {})
515512

516513
# If tracing is enabled all events should go to /envelope endpoint.
517514
# If no tracing is enabled only transactions, events with attachments, and checkins should go to the /envelope endpoint.

sentry_sdk/hub.py

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sentry_sdk.profiler import Profile
1212
from sentry_sdk.tracing import NoOpSpan, Span, Transaction
1313
from sentry_sdk.session import Session
14+
from sentry_sdk.tracing_utils import has_tracing_enabled
1415
from sentry_sdk.utils import (
1516
exc_info_from_error,
1617
event_from_exception,
@@ -322,14 +323,8 @@ def bind_client(
322323
top = self._stack[-1]
323324
self._stack[-1] = (new, top[1])
324325

325-
def capture_event(
326-
self,
327-
event, # type: Event
328-
hint=None, # type: Optional[Hint]
329-
scope=None, # type: Optional[Any]
330-
**scope_args # type: Any
331-
):
332-
# type: (...) -> Optional[str]
326+
def capture_event(self, event, hint=None, scope=None, **scope_args):
327+
# type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str]
333328
"""Captures an event. Alias of :py:meth:`sentry_sdk.Client.capture_event`."""
334329
client, top_scope = self._stack[-1]
335330
scope = _update_scope(top_scope, scope, scope_args)
@@ -341,14 +336,8 @@ def capture_event(
341336
return rv
342337
return None
343338

344-
def capture_message(
345-
self,
346-
message, # type: str
347-
level=None, # type: Optional[str]
348-
scope=None, # type: Optional[Any]
349-
**scope_args # type: Any
350-
):
351-
# type: (...) -> Optional[str]
339+
def capture_message(self, message, level=None, scope=None, **scope_args):
340+
# type: (str, Optional[str], Optional[Scope], Any) -> Optional[str]
352341
"""Captures a message. The message is just a string. If no level
353342
is provided the default level is `info`.
354343
@@ -362,13 +351,8 @@ def capture_message(
362351
{"message": message, "level": level}, scope=scope, **scope_args
363352
)
364353

365-
def capture_exception(
366-
self,
367-
error=None, # type: Optional[Union[BaseException, ExcInfo]]
368-
scope=None, # type: Optional[Any]
369-
**scope_args # type: Any
370-
):
371-
# type: (...) -> Optional[str]
354+
def capture_exception(self, error=None, scope=None, **scope_args):
355+
# type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str]
372356
"""Captures an exception.
373357
374358
:param error: An exception to catch. If `None`, `sys.exc_info()` will be used.
@@ -403,13 +387,8 @@ def _capture_internal_exception(
403387
"""
404388
logger.error("Internal error in sentry_sdk", exc_info=exc_info)
405389

406-
def add_breadcrumb(
407-
self,
408-
crumb=None, # type: Optional[Breadcrumb]
409-
hint=None, # type: Optional[BreadcrumbHint]
410-
**kwargs # type: Any
411-
):
412-
# type: (...) -> None
390+
def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
391+
# type: (Optional[Breadcrumb], Optional[BreadcrumbHint], Any) -> None
413392
"""
414393
Adds a breadcrumb.
415394
@@ -449,13 +428,8 @@ def add_breadcrumb(
449428
while len(scope._breadcrumbs) > max_breadcrumbs:
450429
scope._breadcrumbs.popleft()
451430

452-
def start_span(
453-
self,
454-
span=None, # type: Optional[Span]
455-
instrumenter=INSTRUMENTER.SENTRY, # type: str
456-
**kwargs # type: Any
457-
):
458-
# type: (...) -> Span
431+
def start_span(self, span=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
432+
# type: (Optional[Span], str, Any) -> Span
459433
"""
460434
Create and start timing a new span whose parent is the currently active
461435
span or transaction, if any. The return value is a span instance,
@@ -500,12 +474,9 @@ def start_span(
500474
return Span(**kwargs)
501475

502476
def start_transaction(
503-
self,
504-
transaction=None, # type: Optional[Transaction]
505-
instrumenter=INSTRUMENTER.SENTRY, # type: str
506-
**kwargs # type: Any
477+
self, transaction=None, instrumenter=INSTRUMENTER.SENTRY, **kwargs
507478
):
508-
# type: (...) -> Union[Transaction, NoOpSpan]
479+
# type: (Optional[Transaction], str, Any) -> Union[Transaction, NoOpSpan]
509480
"""
510481
Start and return a transaction.
511482
@@ -577,7 +548,9 @@ def push_scope( # noqa: F811
577548
pass
578549

579550
def push_scope( # noqa
580-
self, callback=None # type: Optional[Callable[[Scope], None]]
551+
self,
552+
callback=None, # type: Optional[Callable[[Scope], None]]
553+
continue_trace=True, # type: bool
581554
):
582555
# type: (...) -> Optional[ContextManager[Scope]]
583556
"""
@@ -595,7 +568,13 @@ def push_scope( # noqa
595568
return None
596569

597570
client, scope = self._stack[-1]
598-
new_layer = (client, copy.copy(scope))
571+
572+
new_scope = copy.copy(scope)
573+
574+
if continue_trace:
575+
new_scope.generate_propagation_context()
576+
577+
new_layer = (client, new_scope)
599578
self._stack.append(new_layer)
600579

601580
return _ScopeManager(self)
@@ -626,7 +605,9 @@ def configure_scope( # noqa: F811
626605
pass
627606

628607
def configure_scope( # noqa
629-
self, callback=None # type: Optional[Callable[[Scope], None]]
608+
self,
609+
callback=None, # type: Optional[Callable[[Scope], None]]
610+
continue_trace=True, # type: bool
630611
):
631612
# type: (...) -> Optional[ContextManager[Scope]]
632613

@@ -639,6 +620,10 @@ def configure_scope( # noqa
639620
"""
640621

641622
client, scope = self._stack[-1]
623+
624+
if continue_trace:
625+
scope.generate_propagation_context()
626+
642627
if callback is not None:
643628
if client is not None:
644629
callback(scope)
@@ -721,18 +706,19 @@ def iter_trace_propagation_headers(self, span=None):
721706
from the span representing the request, if available, or the current
722707
span on the scope if not.
723708
"""
724-
span = span or self.scope.span
725-
if not span:
726-
return
727-
728709
client = self._stack[-1][0]
729-
730710
propagate_traces = client and client.options["propagate_traces"]
731711
if not propagate_traces:
732712
return
733713

734-
for header in span.iter_headers():
735-
yield header
714+
span = span or self.scope.span
715+
716+
if client and has_tracing_enabled(client.options) and span is not None:
717+
for header in span.iter_headers():
718+
yield header
719+
else:
720+
for header in self.scope.iter_headers():
721+
yield header
736722

737723
def trace_propagation_meta(self, span=None):
738724
# type: (Optional[Span]) -> str

sentry_sdk/integrations/aiohttp.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
import weakref
33

4+
from sentry_sdk.api import continue_trace
45
from sentry_sdk._compat import reraise
56
from sentry_sdk.consts import OP
67
from sentry_sdk.hub import Hub
@@ -11,7 +12,7 @@
1112
_filter_headers,
1213
request_body_within_bounds,
1314
)
14-
from sentry_sdk.tracing import SOURCE_FOR_STYLE, Transaction, TRANSACTION_SOURCE_ROUTE
15+
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_ROUTE
1516
from sentry_sdk.utils import (
1617
capture_internal_exceptions,
1718
event_from_exception,
@@ -101,7 +102,7 @@ async def sentry_app_handle(self, request, *args, **kwargs):
101102
scope.clear_breadcrumbs()
102103
scope.add_event_processor(_make_request_processor(weak_request))
103104

104-
transaction = Transaction.continue_from_headers(
105+
transaction = continue_trace(
105106
request.headers,
106107
op=OP.HTTP_SERVER,
107108
# If this transaction name makes it to the UI, AIOHTTP's

sentry_sdk/integrations/asgi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from sentry_sdk._functools import partial
1313
from sentry_sdk._types import TYPE_CHECKING
14+
from sentry_sdk.api import continue_trace
1415
from sentry_sdk.consts import OP
1516
from sentry_sdk.hub import Hub, _should_send_default_pii
1617
from sentry_sdk.integrations._wsgi_common import _filter_headers
@@ -163,7 +164,7 @@ async def _run_app(self, scope, callback):
163164
ty = scope["type"]
164165

165166
if ty in ("http", "websocket"):
166-
transaction = Transaction.continue_from_headers(
167+
transaction = continue_trace(
167168
self._get_headers(scope),
168169
op="{}.server".format(ty),
169170
)

sentry_sdk/integrations/aws_lambda.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from datetime import datetime, timedelta
44
from os import environ
55

6+
from sentry_sdk.api import continue_trace
67
from sentry_sdk.consts import OP
78
from sentry_sdk.hub import Hub, _should_send_default_pii
8-
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction
9-
from sentry_sdk._compat import reraise
9+
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
1010
from sentry_sdk.utils import (
1111
AnnotatedValue,
1212
capture_internal_exceptions,
@@ -16,7 +16,7 @@
1616
)
1717
from sentry_sdk.integrations import Integration
1818
from sentry_sdk.integrations._wsgi_common import _filter_headers
19-
19+
from sentry_sdk._compat import reraise
2020
from sentry_sdk._types import TYPE_CHECKING
2121

2222
if TYPE_CHECKING:
@@ -140,7 +140,8 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs):
140140
# AWS Service may set an explicit `{headers: None}`, we can't rely on `.get()`'s default.
141141
if headers is None:
142142
headers = {}
143-
transaction = Transaction.continue_from_headers(
143+
144+
transaction = continue_trace(
144145
headers,
145146
op=OP.FUNCTION_AWS,
146147
name=aws_context.function_name,

0 commit comments

Comments
 (0)