Skip to content

Commit 54bc81c

Browse files
authored
feat(tracing): Add tracestate header handling (#1179)
This introduces handling of the `tracestate` header, as described in the W3C Trace Context spec[1] and our own corresponding spec[2]. Key features: - Deprecation of `from_traceparent` in favor of `continue_from_headers`, which now propagates both incoming `sentry-trace` and incoming `tracestate` headers. - Propagation of `tracestate` value as a header on outgoing HTTP requests when they're made during a transaction. - Addition of `tracestate` data to transaction envelope headers. Supporting changes: - New utility methods for converting strings to and from base64. - Some refactoring vis-à-vis the links between transactions, span recorders, and spans. See #1173 and #1184. - Moving of some tracing code to a separate `tracing_utils` file. Note: `tracestate` handling is currently feature-gated by the flag `propagate_tracestate` in the `_experiments` SDK option. More details can be found in the main PR on this branch, #971. [1] https://www.w3.org/TR/trace-context/#tracestate-header [2] https://develop.sentry.dev/sdk/performance/trace-context/
1 parent f03c95c commit 54bc81c

File tree

18 files changed

+1304
-327
lines changed

18 files changed

+1304
-327
lines changed

sentry_sdk/client.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sentry_sdk.utils import ContextVar
2323
from sentry_sdk.sessions import SessionFlusher
2424
from sentry_sdk.envelope import Envelope
25+
from sentry_sdk.tracing_utils import has_tracestate_enabled, reinflate_tracestate
2526

2627
from sentry_sdk._types import MYPY
2728

@@ -332,15 +333,29 @@ def capture_event(
332333
attachments = hint.get("attachments")
333334
is_transaction = event_opt.get("type") == "transaction"
334335

336+
# this is outside of the `if` immediately below because even if we don't
337+
# use the value, we want to make sure we remove it before the event is
338+
# sent
339+
raw_tracestate = (
340+
event_opt.get("contexts", {}).get("trace", {}).pop("tracestate", "")
341+
)
342+
343+
# Transactions or events with attachments should go to the /envelope/
344+
# endpoint.
335345
if is_transaction or attachments:
336-
# Transactions or events with attachments should go to the
337-
# /envelope/ endpoint.
338-
envelope = Envelope(
339-
headers={
340-
"event_id": event_opt["event_id"],
341-
"sent_at": format_timestamp(datetime.utcnow()),
342-
}
346+
347+
headers = {
348+
"event_id": event_opt["event_id"],
349+
"sent_at": format_timestamp(datetime.utcnow()),
350+
}
351+
352+
tracestate_data = raw_tracestate and reinflate_tracestate(
353+
raw_tracestate.replace("sentry=", "")
343354
)
355+
if tracestate_data and has_tracestate_enabled():
356+
headers["trace"] = tracestate_data
357+
358+
envelope = Envelope(headers=headers)
344359

345360
if is_transaction:
346361
envelope.add_transaction(event_opt)

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"max_spans": Optional[int],
3333
"record_sql_params": Optional[bool],
3434
"smart_transaction_trimming": Optional[bool],
35+
"propagate_tracestate": Optional[bool],
3536
},
3637
total=False,
3738
)

sentry_sdk/hub.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,8 @@ def iter_trace_propagation_headers(self, span=None):
700700
if not propagate_traces:
701701
return
702702

703-
yield "sentry-trace", span.to_traceparent()
703+
for header in span.iter_headers():
704+
yield header
704705

705706

706707
GLOBAL_HUB = Hub()

sentry_sdk/integrations/django/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from sentry_sdk.hub import Hub, _should_send_default_pii
1010
from sentry_sdk.scope import add_global_event_processor
1111
from sentry_sdk.serializer import add_global_repr_processor
12-
from sentry_sdk.tracing import record_sql_queries
12+
from sentry_sdk.tracing_utils import record_sql_queries
1313
from sentry_sdk.utils import (
1414
HAS_REAL_CONTEXTVARS,
1515
CONTEXTVARS_ERROR_MESSAGE,

sentry_sdk/integrations/httpx.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from sentry_sdk import Hub
22
from sentry_sdk.integrations import Integration, DidNotEnable
3+
from sentry_sdk.utils import logger
34

45
from sentry_sdk._types import MYPY
56

@@ -45,6 +46,11 @@ def send(self, request, **kwargs):
4546
span.set_data("method", request.method)
4647
span.set_data("url", str(request.url))
4748
for key, value in hub.iter_trace_propagation_headers():
49+
logger.debug(
50+
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
51+
key=key, value=value, url=request.url
52+
)
53+
)
4854
request.headers[key] = value
4955
rv = real_send(self, request, **kwargs)
5056

@@ -72,6 +78,11 @@ async def send(self, request, **kwargs):
7278
span.set_data("method", request.method)
7379
span.set_data("url", str(request.url))
7480
for key, value in hub.iter_trace_propagation_headers():
81+
logger.debug(
82+
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
83+
key=key, value=value, url=request.url
84+
)
85+
)
7586
request.headers[key] = value
7687
rv = await real_send(self, request, **kwargs)
7788

sentry_sdk/integrations/sqlalchemy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sentry_sdk._types import MYPY
44
from sentry_sdk.hub import Hub
55
from sentry_sdk.integrations import Integration, DidNotEnable
6-
from sentry_sdk.tracing import record_sql_queries
6+
from sentry_sdk.tracing_utils import record_sql_queries
77

88
try:
99
from sqlalchemy.engine import Engine # type: ignore

sentry_sdk/integrations/stdlib.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from sentry_sdk.hub import Hub
77
from sentry_sdk.integrations import Integration
88
from sentry_sdk.scope import add_global_event_processor
9-
from sentry_sdk.tracing import EnvironHeaders
10-
from sentry_sdk.utils import capture_internal_exceptions, safe_repr
9+
from sentry_sdk.tracing_utils import EnvironHeaders
10+
from sentry_sdk.utils import capture_internal_exceptions, logger, safe_repr
1111

1212
from sentry_sdk._types import MYPY
1313

@@ -86,6 +86,11 @@ def putrequest(self, method, url, *args, **kwargs):
8686
rv = real_putrequest(self, method, url, *args, **kwargs)
8787

8888
for key, value in hub.iter_trace_propagation_headers(span):
89+
logger.debug(
90+
"[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format(
91+
key=key, value=value, real_url=real_url
92+
)
93+
)
8994
self.putheader(key, value)
9095

9196
self._sentrysdk_span = span

sentry_sdk/scope.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,13 @@ def transaction(self):
150150
if self._span is None:
151151
return None
152152

153-
# the span on the scope is itself a transaction
154-
if isinstance(self._span, Transaction):
155-
return self._span
156-
157-
# the span on the scope isn't a transaction but belongs to one
158-
if self._span._containing_transaction:
159-
return self._span._containing_transaction
153+
# there is an orphan span on the scope
154+
if self._span.containing_transaction is None:
155+
return None
160156

161-
# there's a span (not a transaction) on the scope, but it was started on
162-
# its own, not as the descendant of a transaction (this is deprecated
163-
# behavior, but as long as the start_span function exists, it can still
164-
# happen)
165-
return None
157+
# there is either a transaction (which is its own containing
158+
# transaction) or a non-orphan span on the scope
159+
return self._span.containing_transaction
166160

167161
@transaction.setter
168162
def transaction(self, value):
@@ -174,7 +168,7 @@ def transaction(self, value):
174168
# anything set in the scope.
175169
# XXX: note that with the introduction of the Scope.transaction getter,
176170
# there is a semantic and type mismatch between getter and setter. The
177-
# getter returns a transaction, the setter sets a transaction name.
171+
# getter returns a Transaction, the setter sets a transaction name.
178172
# Without breaking version compatibility, we could make the setter set a
179173
# transaction name or transaction (self._span) depending on the type of
180174
# the value argument.

0 commit comments

Comments
 (0)