Skip to content

Commit 51987c5

Browse files
authored
fix(tracing): Get HTTP headers from span rather than transaction if possible (getsentry#1035)
1 parent 1279eec commit 51987c5

File tree

7 files changed

+71
-24
lines changed

7 files changed

+71
-24
lines changed

sentry_sdk/hub.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -682,15 +682,19 @@ def flush(
682682
if client is not None:
683683
return client.flush(timeout=timeout, callback=callback)
684684

685-
def iter_trace_propagation_headers(self):
686-
# type: () -> Generator[Tuple[str, str], None, None]
687-
# TODO: Document
688-
client, scope = self._stack[-1]
689-
span = scope.span
690-
691-
if span is None:
685+
def iter_trace_propagation_headers(self, span=None):
686+
# type: (Optional[Span]) -> Generator[Tuple[str, str], None, None]
687+
"""
688+
Return HTTP headers which allow propagation of trace data. Data taken
689+
from the span representing the request, if available, or the current
690+
span on the scope if not.
691+
"""
692+
span = span or self.scope.span
693+
if not span:
692694
return
693695

696+
client = self._stack[-1][0]
697+
694698
propagate_traces = client and client.options["propagate_traces"]
695699
if not propagate_traces:
696700
return

sentry_sdk/integrations/celery.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ def apply_async(*args, **kwargs):
9696
hub = Hub.current
9797
integration = hub.get_integration(CeleryIntegration)
9898
if integration is not None and integration.propagate_traces:
99-
with hub.start_span(op="celery.submit", description=args[0].name):
99+
with hub.start_span(op="celery.submit", description=args[0].name) as span:
100100
with capture_internal_exceptions():
101-
headers = dict(hub.iter_trace_propagation_headers())
101+
headers = dict(hub.iter_trace_propagation_headers(span))
102102

103103
if headers:
104104
# Note: kwargs can contain headers=None, so no setdefault!

sentry_sdk/integrations/stdlib.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def putrequest(self, method, url, *args, **kwargs):
8585

8686
rv = real_putrequest(self, method, url, *args, **kwargs)
8787

88-
for key, value in hub.iter_trace_propagation_headers():
88+
for key, value in hub.iter_trace_propagation_headers(span):
8989
self.putheader(key, value)
9090

9191
self._sentrysdk_span = span
@@ -178,12 +178,15 @@ def sentry_patched_popen_init(self, *a, **kw):
178178

179179
env = None
180180

181-
for k, v in hub.iter_trace_propagation_headers():
182-
if env is None:
183-
env = _init_argument(a, kw, "env", 10, lambda x: dict(x or os.environ))
184-
env["SUBPROCESS_" + k.upper().replace("-", "_")] = v
185-
186181
with hub.start_span(op="subprocess", description=description) as span:
182+
183+
for k, v in hub.iter_trace_propagation_headers(span):
184+
if env is None:
185+
env = _init_argument(
186+
a, kw, "env", 10, lambda x: dict(x or os.environ)
187+
)
188+
env["SUBPROCESS_" + k.upper().replace("-", "_")] = v
189+
187190
if cwd:
188191
span.set_data("subprocess.cwd", cwd)
189192

tests/conftest.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,15 +368,21 @@ def __init__(self, substring):
368368
self.substring = substring
369369

370370
try:
371-
# unicode only exists in python 2
371+
# the `unicode` type only exists in python 2, so if this blows up,
372+
# we must be in py3 and have the `bytes` type
372373
self.valid_types = (str, unicode) # noqa
373374
except NameError:
374-
self.valid_types = (str,)
375+
self.valid_types = (str, bytes)
375376

376377
def __eq__(self, test_string):
377378
if not isinstance(test_string, self.valid_types):
378379
return False
379380

381+
# this is safe even in py2 because as of 2.6, `bytes` exists in py2
382+
# as an alias for `str`
383+
if isinstance(test_string, bytes):
384+
test_string = test_string.decode()
385+
380386
if len(self.substring) > len(test_string):
381387
return False
382388

tests/integrations/stdlib/test_httplib.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
# py3
1818
from http.client import HTTPSConnection
1919

20-
from sentry_sdk import capture_message
20+
try:
21+
from unittest import mock # python 3.3 and above
22+
except ImportError:
23+
import mock # python < 3.3
24+
25+
from sentry_sdk import capture_message, start_transaction
2126
from sentry_sdk.integrations.stdlib import StdlibIntegration
2227

2328

@@ -110,3 +115,35 @@ def test_httplib_misuse(sentry_init, capture_events):
110115
"status_code": 200,
111116
"reason": "OK",
112117
}
118+
119+
120+
def test_outgoing_trace_headers(
121+
sentry_init, monkeypatch, StringContaining # noqa: N803
122+
):
123+
# HTTPSConnection.send is passed a string containing (among other things)
124+
# the headers on the request. Mock it so we can check the headers, and also
125+
# so it doesn't try to actually talk to the internet.
126+
mock_send = mock.Mock()
127+
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
128+
129+
sentry_init(traces_sample_rate=1.0)
130+
131+
with start_transaction(
132+
name="/interactions/other-dogs/new-dog",
133+
op="greeting.sniff",
134+
trace_id="12312012123120121231201212312012",
135+
) as transaction:
136+
137+
HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")
138+
139+
request_span = transaction._span_recorder.spans[-1]
140+
141+
expected_sentry_trace = (
142+
"sentry-trace: {trace_id}-{parent_span_id}-{sampled}".format(
143+
trace_id=transaction.trace_id,
144+
parent_span_id=request_span.span_id,
145+
sampled=1,
146+
)
147+
)
148+
149+
mock_send.assert_called_with(StringContaining(expected_sentry_trace))

tests/integrations/stdlib/test_subprocess.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,6 @@ def test_subprocess_invalid_args(sentry_init):
183183
sentry_init(integrations=[StdlibIntegration()])
184184

185185
with pytest.raises(TypeError) as excinfo:
186-
subprocess.Popen()
186+
subprocess.Popen(1)
187187

188-
if PY2:
189-
assert "__init__() takes at least 2 arguments (1 given)" in str(excinfo.value)
190-
else:
191-
assert "missing 1 required positional argument: 'args" in str(excinfo.value)
188+
assert "'int' object is not iterable" in str(excinfo.value)

tests/tracing/test_integration_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_continue_from_headers(sentry_init, capture_events, sampled, sample_rate
5858
with start_transaction(name="hi", sampled=True if sample_rate == 0 else None):
5959
with start_span() as old_span:
6060
old_span.sampled = sampled
61-
headers = dict(Hub.current.iter_trace_propagation_headers())
61+
headers = dict(Hub.current.iter_trace_propagation_headers(old_span))
6262

6363
# test that the sampling decision is getting encoded in the header correctly
6464
header = headers["sentry-trace"]

0 commit comments

Comments
 (0)