8000 fix(tracing): Get HTTP headers from span rather than transaction if possible by lobsterkatie · Pull Request #1035 · getsentry/sentry-python · GitHub
[go: up one dir, main page]

Skip to content

fix(tracing): Get HTTP headers from span rather than transaction if possible #1035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,15 +682,19 @@ def flush(
if client is not None:
return client.flush(timeout=timeout, callback=callback)

def iter_trace_propagation_headers(self):
# type: () -> Generator[Tuple[str, str], None, None]
# TODO: Document
client, scope = self._stack[-1]
span = scope.span

if span is None:
def iter_trace_propagation_headers(self, span=None):
# type: (Optional[Span]) -> Generator[Tuple[str, str], None, None]
"""
Return HTTP headers which allow propagation of trace data. Data taken
from the span representing the request, if available, or the current
span on the scope if not.
"""
span = span or self.scope.span
if not span:
return

client = self._stack[-1][0]

propagate_traces = client and client.options["propagate_traces"]
if not propagate_traces:
return
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/celery.py
10000
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ def apply_async(*args, **kwargs):
hub = Hub.current
integration = hub.get_integration(CeleryIntegration)
if integration is not None and integration.propagate_traces:
with hub.start_span(op="celery.submit", description=args[0].name):
with hub.start_span(op="celery.submit", description=args[0].name) as span:
with capture_internal_exceptions():
headers = dict(hub.iter_trace_propagation_headers())
headers = dict(hub.iter_trace_propagation_headers(span))

if headers:
# Note: kwargs can contain headers=None, so no setdefault!
Expand Down
15 changes: 9 additions & 6 deletions sentry_sdk/integrations/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def putrequest(self, method, url, *args, **kwargs):

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

for key, value in hub.iter_trace_propagation_headers():
for key, value in hub.iter_trace_propagation_headers(span):
self.putheader(key, value)

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

env = None

for k, v in hub.iter_trace_propagation_headers():
if env is None:
env = _init_argument(a, kw, "env", 10, lambda x: dict(x or os.environ))
env["SUBPROCESS_" + k.upper().replace("-", "_")] = v

with hub.start_span(op="subprocess", description=description) as span:

for k, v in hub.iter_trace_propagation_headers(span):
if env is None:
env = _init_argument(
a, kw, "env", 10, lambda x: dict(x or os.environ)
)
env["SUBPROCESS_" + k.upper().replace("-", "_")] = v

if cwd:
span.set_data("subprocess.cwd", cwd)

Expand Down
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,15 +368,21 @@ def __init__(self, substring):
self.substring = substring

try:
# unicode only exists in python 2
# the `unicode` type only exists in python 2, so if this blows up,
# we must be in py3 and have the `bytes` type
self.valid_types = (str, unicode) # noqa
except NameError:
self.valid_types = (str,)
self.valid_types = (str, bytes)

def __eq__(self, test_string):
if not isinstance(test_string, self.valid_types):
return False

# this is safe even in py2 because as of 2.6, `bytes` exists in py2
# as an alias for `str`
if isinstance(test_string, bytes):
test_string = test_string.decode()

if len(self.substring) > len(test_string):
return False

Expand Down
39 changes: 38 additions & 1 deletion tests/integrations/stdlib/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
# py3
from http.client import HTTPSConnection

from sentry_sdk import capture_message
try:
from unittest import mock # python 3.3 and above
except ImportError:
import mock # python < 3.3

from sentry_sdk import capture_message, start_transaction
from sentry_sdk.integrations.stdlib import StdlibIntegration


Expand Down Expand Up @@ -110,3 +115,35 @@ def test_httplib_misuse(sentry_init, capture_events):
"status_code": 200,
"reason": "OK",
}


def test_outgoing_trace_headers(
sentry_init, monkeypatch, StringContaining # noqa: N803
):
# HTTPSConnection.send is passed a string containing (among other things)
# the headers on the request. Mock it so we can check the headers, and also
# so it doesn't try to actually talk to the internet.
mock_send = mock.Mock()
monkeypatch.setattr(HTTPSConnection, "send", mock_send)

sentry_init(traces_sample_rate=1.0)

with start_transaction(
name="/interactions/other-dogs/new-dog",
op="greeting.sniff",
trace_id="12312012123120121231201212312012",
) as transaction:

HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")

request_span = transaction._span_recorder.spans[-1]

expected_sentry_trace = (
"sentry-trace: {trace_id}-{parent_span_id}-{sampled}".format(
trace_id=transaction.trace_id,
parent_span_id=request_span.span_id,
sampled=1,
)
)

mock_s 6D47 end.assert_called_with(StringContaining(expected_sentry_trace))
7 changes: 2 additions & 5 deletions tests/integrations/stdlib/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,6 @@ def test_subprocess_invalid_args(sentry_init):
sentry_init(integrations=[StdlibIntegration()])

with pytest.raises(TypeError) as excinfo:
subprocess.Popen()
subprocess.Popen(1)

if PY2:
assert "__init__() takes at least 2 arguments (1 given)" in str(excinfo.value)
else:
assert "missing 1 required positional argument: 'args" in str(excinfo.value)
assert "'int' object is not iterable" in str(excinfo.value)
8361 2 changes: 1 addition & 1 deletion tests/tracing/test_integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_continue_from_headers(sentry_init, capture_events, sampled, sample_rate
with start_transaction(name="hi", sampled=True if sample_rate == 0 else None):
with start_span() as old_span:
old_span.sampled = sampled
headers = dict(Hub.current.iter_trace_propagation_headers())
headers = dict(Hub.current.iter_trace_propagation_headers(old_span))

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