Skip to content

Commit 7c2d770

Browse files
authored
Fix httplib (#3820)
1 parent 59f84d4 commit 7c2d770

File tree

4 files changed

+110
-113
lines changed

4 files changed

+110
-113
lines changed

sentry_sdk/integrations/opentelemetry/utils.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -161,36 +161,43 @@ def span_data_for_http_method(span):
161161
# type: (ReadableSpan) -> OtelExtractedSpanData
162162
span_attributes = span.attributes or {}
163163

164-
op = "http"
164+
op = span_attributes.get(SentrySpanAttribute.OP)
165+
if op is None:
166+
op = "http"
165167

166-
if span.kind == SpanKind.SERVER:
167-
op += ".server"
168-
elif span.kind == SpanKind.CLIENT:
169-
op += ".client"
168+
if span.kind == SpanKind.SERVER:
169+
op += ".server"
170+
elif span.kind == SpanKind.CLIENT:
171+
op += ".client"
170172

171173
http_method = span_attributes.get(SpanAttributes.HTTP_METHOD)
172174
route = span_attributes.get(SpanAttributes.HTTP_ROUTE)
173175
target = span_attributes.get(SpanAttributes.HTTP_TARGET)
174176
peer_name = span_attributes.get(SpanAttributes.NET_PEER_NAME)
175177

176-
description = f"{http_method}"
177-
178-
if route:
179-
description = f"{http_method} {route}"
180-
elif target:
181-
description = f"{http_method} {target}"
182-
elif peer_name:
183-
description = f"{http_method} {peer_name}"
184-
else:
185-
url = span_attributes.get(SpanAttributes.HTTP_URL)
186-
url = cast("Optional[str]", url)
187-
188-
if url:
189-
parsed_url = urlparse(url)
190-
url = "{}://{}{}".format(
191-
parsed_url.scheme, parsed_url.netloc, parsed_url.path
192-
)
193-
description = f"{http_method} {url}"
178+
# TODO-neel-potel remove description completely
179+
description = span_attributes.get(
180+
SentrySpanAttribute.DESCRIPTION
181+
) or span_attributes.get(SentrySpanAttribute.NAME)
182+
if description is None:
183+
description = f"{http_method}"
184+
185+
if route:
186+
description = f"{http_method} {route}"
187+
elif target:
188+
description = f"{http_method} {target}"
189+
elif peer_name:
190+
description = f"{http_method} {peer_name}"
191+
else:
192+
url = span_attributes.get(SpanAttributes.HTTP_URL)
193+
url = cast("Optional[str]", url)
194+
195+
if url:
196+
parsed_url = urlparse(url)
197+
url = "{}://{}{}".format(
198+
parsed_url.scheme, parsed_url.netloc, parsed_url.path
199+
)
200+
description = f"{http_method} {url}"
194201

195202
status, http_status = extract_span_status(span)
196203

sentry_sdk/integrations/stdlib.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def putrequest(self, method, url, *args, **kwargs):
9696
origin="auto.http.stdlib.httplib",
9797
only_if_parent=True,
9898
)
99+
span.__enter__()
99100

100101
data = {
101102
SPANDATA.HTTP_METHOD: method,
@@ -152,7 +153,7 @@ def getresponse(self, *args, **kwargs):
152153
span.set_http_status(int(rv.status))
153154
span.set_data("reason", rv.reason)
154155
finally:
155-
span.finish()
156+
span.__exit__(None, None, None)
156157

157158
return rv
158159

tests/integrations/httpx/test_httpx.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ def test_outgoing_trace_headers(sentry_init, httpx_client, capture_envelopes):
101101
(httpx.Client(), httpx.AsyncClient()),
102102
)
103103
def test_outgoing_trace_headers_append_to_baggage(
104-
sentry_init, httpx_client, capture_envelopes, SortedBaggage, # noqa: N803
104+
sentry_init,
105+
httpx_client,
106+
capture_envelopes,
107+
SortedBaggage, # noqa: N803
105108
):
106109
sentry_init(
107110
traces_sample_rate=1.0,
@@ -137,9 +140,8 @@ def test_outgoing_trace_headers_append_to_baggage(
137140
parent_span_id=request_span["span_id"],
138141
sampled=1,
139142
)
140-
assert (
141-
response.request.headers["baggage"]
142-
== SortedBaggage(f"custom=data,sentry-trace_id={trace_id},sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true")
143+
assert response.request.headers["baggage"] == SortedBaggage(
144+
f"custom=data,sentry-trace_id={trace_id},sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true"
143145
)
144146

145147

tests/integrations/stdlib/test_httplib.py

Lines changed: 72 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import pytest
88

9-
from sentry_sdk import capture_message, start_transaction
9+
from sentry_sdk import capture_message, start_span, continue_trace, isolation_scope
1010
from sentry_sdk.consts import MATCH_ALL, SPANDATA
1111
from sentry_sdk.tracing import Transaction
1212
from sentry_sdk.integrations.stdlib import StdlibIntegration
@@ -16,6 +16,31 @@
1616
PORT = create_mock_http_server()
1717

1818

19+
@pytest.fixture
20+
def capture_request_headers(monkeypatch):
21+
"""
22+
HTTPConnection.send is passed a string containing (among other things)
23+
the headers on the request. Mock it so we can check the headers.
24+
"""
25+
26+
def inner(do_send=True):
27+
request_headers = {}
28+
old_send = HTTPConnection.send
29+
30+
def patched_send(self, data):
31+
for line in data.decode("utf-8").split("\r\n")[1:]:
32+
if line:
33+
key, val = line.split(": ")
34+
request_headers[key] = val
35+
if do_send:
36+
old_send(self, data)
37+
38+
monkeypatch.setattr(HTTPConnection, "send", patched_send)
39+
return request_headers
40+
41+
return inner
42+
43+
1944
def test_crumb_capture(sentry_init, capture_events):
2045
sentry_init(integrations=[StdlibIntegration()])
2146
events = capture_events()
@@ -79,7 +104,7 @@ def test_empty_realurl(sentry_init):
79104
"""
80105

81106
sentry_init(dsn="")
82-
HTTPConnection("example.com", port=443).putrequest("POST", None)
107+
HTTPConnection("localhost", PORT).putrequest("POST", None)
83108

84109

85110
def test_httplib_misuse(sentry_init, capture_events, request):
@@ -131,40 +156,28 @@ def test_httplib_misuse(sentry_init, capture_events, request):
131156
)
132157

133158

134-
def test_outgoing_trace_headers(sentry_init, monkeypatch, capture_envelopes):
135-
# HTTPSConnection.send is passed a string containing (among other things)
136-
# the headers on the request. Mock it so we can check the headers, and also
137-
# so it doesn't try to actually talk to the internet.
138-
mock_send = mock.Mock()
139-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
140-
159+
def test_outgoing_trace_headers(
160+
sentry_init, capture_envelopes, capture_request_headers, SortedBaggage
161+
):
141162
sentry_init(traces_sample_rate=1.0)
142-
143163
envelopes = capture_envelopes()
164+
request_headers = capture_request_headers()
144165

145166
headers = {}
167+
headers["sentry-trace"] = "771a43a4192642f0b136d5159a501700-1234567890abcdef-1"
146168
headers["baggage"] = (
147169
"other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
148-
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
170+
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, "
171+
"sentry-sampled=true, sentry-sample_rate=0.01337, "
149172
"sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
150173
)
151174

152-
transaction = Transaction.continue_from_headers(headers)
153-
154-
with start_transaction(
155-
transaction=transaction,
156-
name="/interactions/other-dogs/new-dog",
157-
op="greeting.sniff",
158-
trace_id="12312012123120121231201212312012",
159-
) as transaction:
160-
HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")
161-
162-
(request_str,) = mock_send.call_args[0]
163-
request_headers = {}
164-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
165-
if line:
166-
key, val = line.split(": ")
167-
request_headers[key] = val
175+
with isolation_scope():
176+
with continue_trace(headers):
177+
with start_span(name="/interactions/other-dogs/new-dog"):
178+
conn = HTTPConnection("localhost", PORT)
179+
conn.request("GET", "/top-chasers")
180+
conn.getresponse()
168181

169182
(envelope,) = envelopes
170183
transaction = envelope.get_transaction_event()
@@ -180,38 +193,30 @@ def test_outgoing_trace_headers(sentry_init, monkeypatch, capture_envelopes):
180193
expected_outgoing_baggage = (
181194
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
182195
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
196+
"sentry-sampled=true,"
183197
"sentry-sample_rate=0.01337,"
184198
"sentry-user_id=Am%C3%A9lie"
185199
)
186200

187-
assert request_headers["baggage"] == expected_outgoing_baggage
201+
assert request_headers["baggage"] == SortedBaggage(expected_outgoing_baggage)
188202

189203

190-
def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch, capture_envelopes):
191-
# HTTPSConnection.send is passed a string containing (among other things)
192-
# the headers on the request. Mock it so we can check the headers, and also
193-
# so it doesn't try to actually talk to the internet.
194-
mock_send = mock.Mock()
195-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
196-
204+
def test_outgoing_trace_headers_head_sdk(
205+
sentry_init, monkeypatch, capture_request_headers, capture_envelopes, SortedBaggage
206+
):
197207
# make sure transaction is always sampled
198208
monkeypatch.setattr(random, "random", lambda: 0.1)
199209

200210
sentry_init(traces_sample_rate=0.5, release="foo")
201-
202211
envelopes = capture_envelopes()
212+
request_headers = capture_request_headers()
203213

204-
transaction = Transaction.continue_from_headers({})
205-
206-
with start_transaction(transaction=transaction, name="Head SDK tx") as transaction:
207-
HTTPSConnection("www.squirrelchasers.com").request("GET", "/top-chasers")
208-
209-
(request_str,) = mock_send.call_args[0]
210-
request_headers = {}
211-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
212-
if line:
213-
key, val = line.split(": ")
214-
request_headers[key] = val
214+
with isolation_scope():
215+
with continue_trace({}):
216+
with start_span(name="Head SDK tx") as root_span:
217+
conn = HTTPConnection("localhost", PORT)
218+
conn.request("GET", "/top-chasers")
219+
conn.getresponse()
215220

216221
(envelope,) = envelopes
217222
transaction = envelope.get_transaction_event()
@@ -225,14 +230,15 @@ def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch, capture_envel
225230
assert request_headers["sentry-trace"] == expected_sentry_trace
226231

227232
expected_outgoing_baggage = (
228-
"sentry-trace_id=%s,"
233+
f"sentry-trace_id={root_span.trace_id},"
229234
"sentry-environment=production,"
230235
"sentry-release=foo,"
231236
"sentry-sample_rate=0.5,"
232-
"sentry-sampled=%s"
233-
) % (transaction.trace_id, "true" if transaction.sampled else "false")
237+
"sentry-sampled=true,"
238+
"sentry-transaction=Head%20SDK%20tx"
239+
)
234240

235-
assert request_headers["baggage"] == expected_outgoing_baggage
241+
assert request_headers["baggage"] == SortedBaggage(expected_outgoing_baggage)
236242

237243

238244
@pytest.mark.parametrize(
@@ -295,42 +301,23 @@ def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch, capture_envel
295301
],
296302
)
297303
def test_option_trace_propagation_targets(
298-
sentry_init, monkeypatch, trace_propagation_targets, host, path, trace_propagated
304+
sentry_init,
305+
capture_request_headers,
306+
trace_propagation_targets,
307+
host,
308+
path,
309+
trace_propagated,
299310
):
300-
# HTTPSConnection.send is passed a string containing (among other things)
301-
# the headers on the request. Mock it so we can check the headers, and also
302-
# so it doesn't try to actually talk to the internet.
303-
mock_send = mock.Mock()
304-
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
305-
306311
sentry_init(
307312
trace_propagation_targets=trace_propagation_targets,
308313
traces_sample_rate=1.0,
309314
)
310315

311-
headers = {
312-
"baggage": (
313-
"sentry-trace_id=771a43a4192642f0b136d5159a501700, "
314-
"sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
315-
)
316-
}
317-
318-
transaction = Transaction.continue_from_headers(headers)
319-
320-
with start_transaction(
321-
transaction=transaction,
322-
name="/interactions/other-dogs/new-dog",
323-
op="greeting.sniff",
324-
trace_id="12312012123120121231201212312012",
325-
) as transaction:
326-
HTTPSConnection(host).request("GET", path)
316+
request_headers = capture_request_headers(do_send=False)
327317

328-
(request_str,) = mock_send.call_args[0]
329-
request_headers = {}
330-
for line in request_str.decode("utf-8").split("\r\n")[1:]:
331-
if line:
332-
key, val = line.split(": ")
333-
request_headers[key] = val
318+
with start_span(name="foo"):
319+
HTTPSConnection(host).request("GET", path)
320+
# don't invoke getresponse to avoid actual network traffic
334321

335322
if trace_propagated:
336323
assert "sentry-trace" in request_headers
@@ -344,8 +331,8 @@ def test_span_origin(sentry_init, capture_events):
344331
sentry_init(traces_sample_rate=1.0, debug=True)
345332
events = capture_events()
346333

347-
with start_transaction(name="foo"):
348-
conn = HTTPSConnection("example.com")
334+
with start_span(name="foo"):
335+
conn = HTTPConnection("localhost", PORT)
349336
conn.request("GET", "/foo")
350337
conn.getresponse()
351338

@@ -364,9 +351,9 @@ def test_http_timeout(monkeypatch, sentry_init, capture_envelopes):
364351

365352
envelopes = capture_envelopes()
366353

367-
with start_transaction(op="op", name="name"):
354+
with start_span(op="op", name="name"):
368355
try:
369-
conn = HTTPSConnection("www.squirrelchasers.com")
356+
conn = HTTPConnection("localhost", PORT)
370357
conn.request("GET", "/top-chasers")
371358
conn.getresponse()
372359
except Exception:
@@ -385,4 +372,4 @@ def test_http_timeout(monkeypatch, sentry_init, capture_envelopes):
385372

386373
span = transaction["spans"][0]
387374
assert span["op"] == "http.client"
388-
assert span["description"] == "GET https://www.squirrelchasers.com/top-chasers"
375+
assert span["description"] == f"GET http://localhost:{PORT}/top-chasers"

0 commit comments

Comments
 (0)