Skip to content

Commit 2f5afda

Browse files
authored
fix(httpx,httpx2): Always capture breadcrumb (#7351)
Currently, if there is no current span, we don't capture an HTTP client breadcrumb in HTTPX and HTTPX2 integrations because we return early. This is essentially the same change 4 times (httpx sync, httpx async, httpx2 sync, http2 async).
1 parent 320605f commit 2f5afda

4 files changed

Lines changed: 406 additions & 99 deletions

File tree

‎sentry_sdk/integrations/httpx.py‎

Lines changed: 63 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
SENSITIVE_DATA_SUBSTITUTE,
1515
capture_internal_exceptions,
1616
ensure_integration_enabled,
17+
nullcontext,
1718
parse_url,
1819
)
1920

@@ -61,39 +62,46 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
6162

6263
if is_span_streaming_enabled:
6364
if sentry_sdk.traces.get_current_span() is None:
64-
propagate_trace_headers(client, request)
65-
return real_send(self, request, **kwargs)
65+
span_ctx = nullcontext()
66+
else:
67+
span_ctx = sentry_sdk.traces.start_span(
68+
name="%s %s"
69+
% (
70+
request.method,
71+
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
72+
),
73+
attributes={
74+
"sentry.op": OP.HTTP_CLIENT,
75+
"sentry.origin": HttpxIntegration.origin,
76+
"http.request.method": request.method,
77+
},
78+
)
6679

6780
url_attributes = get_url_attributes(client, parsed_url)
6881

69-
with sentry_sdk.traces.start_span(
70-
name="%s %s"
71-
% (
72-
request.method,
73-
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
74-
),
75-
attributes={
76-
"sentry.op": OP.HTTP_CLIENT,
77-
"sentry.origin": HttpxIntegration.origin,
78-
"http.request.method": request.method,
79-
},
80-
) as streamed_span:
81-
attributes: "Attributes" = dict(url_attributes)
82-
82+
with span_ctx as streamed_span:
8383
propagate_trace_headers(client, request)
8484

8585
try:
8686
rv = real_send(self, request, **kwargs)
8787

88-
streamed_span.status = "error" if rv.status_code >= 400 else "ok"
89-
attributes["http.response.status_code"] = rv.status_code
88+
if streamed_span is not None:
89+
streamed_span.status = (
90+
"error" if rv.status_code >= 400 else "ok"
91+
)
92+
streamed_span.set_attribute(
93+
"http.response.status_code", rv.status_code
94+
)
9095
finally:
91-
streamed_span.set_attributes(attributes)
96+
if streamed_span is not None:
97+
streamed_span.set_attributes(url_attributes)
98+
99+
if streamed_span is not None:
100+
# Needs to happen within the context manager as we want to attach the
101+
# final data before the span finishes and is sent for ingesting.
102+
with capture_internal_exceptions():
103+
add_http_request_source(streamed_span)
92104

93-
# Needs to happen within the context manager as we want to attach the
94-
# final data before the span finishes and is sent for ingesting.
95-
with capture_internal_exceptions():
96-
add_http_request_source(streamed_span)
97105
else:
98106
with sentry_sdk.start_span(
99107
op=OP.HTTP_CLIENT,
@@ -138,7 +146,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
138146
elif url_attributes:
139147
breadcrumb_data.update(
140148
{
141-
"url": url_attributes.get("url.full", parsed_url.url),
149+
"url": url_attributes.get("url.full", ""),
142150
SPANDATA.HTTP_QUERY: url_attributes.get("url.query", ""),
143151
SPANDATA.HTTP_FRAGMENT: url_attributes.get("url.fragment", ""),
144152
}
@@ -170,39 +178,45 @@ async def send(
170178

171179
if is_span_streaming_enabled:
172180
if sentry_sdk.traces.get_current_span() is None:
173-
propagate_trace_headers(client, request)
174-
return await real_send(self, request, **kwargs)
181+
span_ctx = nullcontext()
182+
else:
183+
span_ctx = sentry_sdk.traces.start_span(
184+
name="%s %s"
185+
% (
186+
request.method,
187+
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
188+
),
189+
attributes={
190+
"sentry.op": OP.HTTP_CLIENT,
191+
"sentry.origin": HttpxIntegration.origin,
192+
"http.request.method": request.method,
193+
},
194+
)
175195

176196
url_attributes = get_url_attributes(client, parsed_url)
177197

178-
with sentry_sdk.traces.start_span(
179-
name="%s %s"
180-
% (
181-
request.method,
182-
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
183-
),
184-
attributes={
185-
"sentry.op": OP.HTTP_CLIENT,
186-
"sentry.origin": HttpxIntegration.origin,
187-
"http.request.method": request.method,
188-
},
189-
) as streamed_span:
190-
attributes: "Attributes" = dict(url_attributes)
191-
198+
with span_ctx as streamed_span:
192199
propagate_trace_headers(client, request)
193200

194201
try:
195202
rv = await real_send(self, request, **kwargs)
196203

197-
streamed_span.status = "error" if rv.status_code >= 400 else "ok"
198-
attributes["http.response.status_code"] = rv.status_code
204+
if streamed_span is not None:
205+
streamed_span.status = (
206+
"error" if rv.status_code >= 400 else "ok"
207+
)
208+
streamed_span.set_attribute(
209+
"http.response.status_code", rv.status_code
210+
)
199211
finally:
200-
streamed_span.set_attributes(attributes)
201-
202-
# Needs to happen within the context manager as we want to attach the
203-
# final data before the span finishes and is sent for ingesting.
204-
with capture_internal_exceptions():
205-
add_http_request_source(streamed_span)
212+
if streamed_span is not None:
213+
streamed_span.set_attributes(url_attributes)
214+
215+
if streamed_span is not None:
216+
# Needs to happen within the context manager as we want to attach the
217+
# final data before the span finishes and is sent for ingesting.
218+
with capture_internal_exceptions():
219+
add_http_request_source(streamed_span)
206220
else:
207221
with sentry_sdk.start_span(
208222
op=OP.HTTP_CLIENT,
@@ -246,7 +260,7 @@ async def send(
246260
elif url_attributes:
247261
breadcrumb_data.update(
248262
{
249-
"url": url_attributes.get("url.full", parsed_url.url),
263+
"url": url_attributes.get("url.full", ""),
250264
SPANDATA.HTTP_QUERY: url_attributes.get("url.query", ""),
251265
SPANDATA.HTTP_FRAGMENT: url_attributes.get("url.fragment", ""),
252266
}

‎sentry_sdk/integrations/httpx2.py‎

Lines changed: 63 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
SENSITIVE_DATA_SUBSTITUTE,
1515
capture_internal_exceptions,
1616
ensure_integration_enabled,
17+
nullcontext,
1718
parse_url,
1819
)
1920

@@ -61,39 +62,45 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
6162

6263
if is_span_streaming_enabled:
6364
if sentry_sdk.traces.get_current_span() is None:
64-
propagate_trace_headers(client, request)
65-
return real_send(self, request, **kwargs)
65+
span_ctx = nullcontext()
66+
else:
67+
span_ctx = sentry_sdk.traces.start_span(
68+
name="%s %s"
69+
% (
70+
request.method,
71+
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
72+
),
73+
attributes={
74+
"sentry.op": OP.HTTP_CLIENT,
75+
"sentry.origin": Httpx2Integration.origin,
76+
"http.request.method": request.method,
77+
},
78+
)
6679

6780
url_attributes = get_url_attributes(client, parsed_url)
6881

69-
with sentry_sdk.traces.start_span(
70-
name="%s %s"
71-
% (
72-
request.method,
73-
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
74-
),
75-
attributes={
76-
"sentry.op": OP.HTTP_CLIENT,
77-
"sentry.origin": Httpx2Integration.origin,
78-
"http.request.method": request.method,
79-
},
80-
) as streamed_span:
81-
attributes: "Attributes" = dict(url_attributes)
82-
82+
with span_ctx as streamed_span:
8383
propagate_trace_headers(client, request)
8484

8585
try:
8686
rv = real_send(self, request, **kwargs)
8787

88-
streamed_span.status = "error" if rv.status_code >= 400 else "ok"
89-
attributes["http.response.status_code"] = rv.status_code
88+
if streamed_span is not None:
89+
streamed_span.status = (
90+
"error" if rv.status_code >= 400 else "ok"
91+
)
92+
streamed_span.set_attribute(
93+
"http.response.status_code", rv.status_code
94+
)
9095
finally:
91-
streamed_span.set_attributes(attributes)
92-
93-
# Needs to happen within the context manager as we want to attach the
94-
# final data before the span finishes and is sent for ingesting.
95-
with capture_internal_exceptions():
96-
add_http_request_source(streamed_span)
96+
if streamed_span is not None:
97+
streamed_span.set_attributes(url_attributes)
98+
99+
if streamed_span is not None:
100+
# Needs to happen within the context manager as we want to attach the
101+
# final data before the span finishes and is sent for ingesting.
102+
with capture_internal_exceptions():
103+
add_http_request_source(streamed_span)
97104
else:
98105
with sentry_sdk.start_span(
99106
op=OP.HTTP_CLIENT,
@@ -138,7 +145,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
138145
elif url_attributes:
139146
breadcrumb_data.update(
140147
{
141-
"url": url_attributes.get("url.full", parsed_url.url),
148+
"url": url_attributes.get("url.full", ""),
142149
SPANDATA.HTTP_QUERY: url_attributes.get("url.query", ""),
143150
SPANDATA.HTTP_FRAGMENT: url_attributes.get("url.fragment", ""),
144151
}
@@ -170,39 +177,45 @@ async def send(
170177

171178
if is_span_streaming_enabled:
172179
if sentry_sdk.traces.get_current_span() is None:
173-
propagate_trace_headers(client, request)
174-
return await real_send(self, request, **kwargs)
180+
span_ctx = nullcontext()
181+
else:
182+
span_ctx = sentry_sdk.traces.start_span(
183+
name="%s %s"
184+
% (
185+
request.method,
186+
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
187+
),
188+
attributes={
189+
"sentry.op": OP.HTTP_CLIENT,
190+
"sentry.origin": Httpx2Integration.origin,
191+
"http.request.method": request.method,
192+
},
193+
)
175194

176195
url_attributes = get_url_attributes(client, parsed_url)
177196

178-
with sentry_sdk.traces.start_span(
179-
name="%s %s"
180-
% (
181-
request.method,
182-
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
183-
),
184-
attributes={
185-
"sentry.op": OP.HTTP_CLIENT,
186-
"sentry.origin": Httpx2Integration.origin,
187-
"http.request.method": request.method,
188-
},
189-
) as streamed_span:
190-
attributes: "Attributes" = dict(url_attributes)
191-
197+
with span_ctx as streamed_span:
192198
propagate_trace_headers(client, request)
193199

194200
try:
195201
rv = await real_send(self, request, **kwargs)
196202

197-
streamed_span.status = "error" if rv.status_code >= 400 else "ok"
198-
attributes["http.response.status_code"] = rv.status_code
203+
if streamed_span is not None:
204+
streamed_span.status = (
205+
"error" if rv.status_code >= 400 else "ok"
206+
)
207+
streamed_span.set_attribute(
208+
"http.response.status_code", rv.status_code
209+
)
199210
finally:
200-
streamed_span.set_attributes(attributes)
201-
202-
# Needs to happen within the context manager as we want to attach the
203-
# final data before the span finishes and is sent for ingesting.
204-
with capture_internal_exceptions():
205-
add_http_request_source(streamed_span)
211+
if streamed_span is not None:
212+
streamed_span.set_attributes(url_attributes)
213+
214+
if streamed_span is not None:
215+
# Needs to happen within the context manager as we want to attach the
216+
# final data before the span finishes and is sent for ingesting.
217+
with capture_internal_exceptions():
218+
add_http_request_source(streamed_span)
206219
else:
207220
with sentry_sdk.start_span(
208221
op=OP.HTTP_CLIENT,
@@ -246,7 +259,7 @@ async def send(
246259
elif url_attributes:
247260
breadcrumb_data.update(
248261
{
249-
"url": url_attributes.get("url.full", parsed_url.url),
262+
"url": url_attributes.get("url.full", ""),
250263
SPANDATA.HTTP_QUERY: url_attributes.get("url.query", ""),
251264
SPANDATA.HTTP_FRAGMENT: url_attributes.get("url.fragment", ""),
252265
}

0 commit comments

Comments
 (0)