Skip to content

Commit 43ca6f3

Browse files
authored
Fix breadcrumbs in HTTP clients (#3683)
This moves the creation of breadcrumbs for outgoing HTTP requests from the `maybe_create_breadcrumbs_from_span` into the integrations.
1 parent 6b3aab6 commit 43ca6f3

File tree

12 files changed

+141
-33
lines changed

12 files changed

+141
-33
lines changed

sentry_sdk/integrations/aiohttp.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,17 @@ async def on_request_start(session, trace_config_ctx, params):
229229
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
230230
origin=AioHttpIntegration.origin,
231231
)
232-
span.set_data(SPANDATA.HTTP_METHOD, method)
232+
233+
data = {
234+
SPANDATA.HTTP_METHOD: method,
235+
}
233236
if parsed_url is not None:
234-
span.set_data("url", parsed_url.url)
235-
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
236-
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
237+
data["url"] = parsed_url.url
238+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
239+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
240+
241+
for key, value in data.items():
242+
span.set_data(key, value)
237243

238244
client = sentry_sdk.get_client()
239245

@@ -258,12 +264,23 @@ async def on_request_start(session, trace_config_ctx, params):
258264
params.headers[key] = value
259265

260266
trace_config_ctx.span = span
267+
trace_config_ctx.span_data = data
261268

262269
async def on_request_end(session, trace_config_ctx, params):
263270
# type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None
264271
if trace_config_ctx.span is None:
265272
return
266273

274+
span_data = trace_config_ctx.span_data or {}
275+
span_data[SPANDATA.HTTP_STATUS_CODE] = int(params.response.status)
276+
span_data["reason"] = params.response.reason
277+
278+
sentry_sdk.add_breadcrumb(
279+
type="http",
280+
category="httplib",
281+
data=span_data,
282+
)
283+
267284
span = trace_config_ctx.span
268285
span.set_http_status(int(params.response.status))
269286
span.set_data("reason", params.response.reason)

sentry_sdk/integrations/boto3.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,20 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs):
7474
origin=Boto3Integration.origin,
7575
)
7676

77+
data = {
78+
SPANDATA.HTTP_METHOD: request.method,
79+
}
7780
with capture_internal_exceptions():
7881
parsed_url = parse_url(request.url, sanitize=False)
79-
span.set_data("aws.request.url", parsed_url.url)
80-
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
81-
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
82+
data["aws.request.url"] = parsed_url.url
83+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
84+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
85+
86+
for key, value in data.items():
87+
span.set_data(key, value)
8288

8389
span.set_tag("aws.service_id", service_id)
8490
span.set_tag("aws.operation_name", operation_name)
85-
span.set_data(SPANDATA.HTTP_METHOD, request.method)
8691

8792
# We do it in order for subsequent http calls/retries be
8893
# attached to this span.
@@ -91,6 +96,7 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs):
9196
# request.context is an open-ended data-structure
9297
# where we can add anything useful in request life cycle.
9398
request.context["_sentrysdk_span"] = span
99+
request.context["_sentrysdk_span_data"] = data
94100

95101

96102
def _sentry_after_call(context, parsed, **kwargs):
@@ -100,6 +106,15 @@ def _sentry_after_call(context, parsed, **kwargs):
100106
# Span could be absent if the integration is disabled.
101107
if span is None:
102108
return
109+
110+
span_data = context.pop("_sentrysdk_span_data", {})
111+
112+
sentry_sdk.add_breadcrumb(
113+
type="http",
114+
category="httplib",
115+
data=span_data,
116+
)
117+
103118
span.__exit__(None, None, None)
104119

105120
body = parsed.get("Body")
@@ -143,4 +158,13 @@ def _sentry_after_call_error(context, exception, **kwargs):
143158
# Span could be absent if the integration is disabled.
144159
if span is None:
145160
return
161+
162+
span_data = context.pop("_sentrysdk_span_data", {})
163+
164+
sentry_sdk.add_breadcrumb(
165+
type="http",
166+
category="httplib",
167+
data=span_data,
168+
)
169+
146170
span.__exit__(type(exception), exception, None)

sentry_sdk/integrations/httpx.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,16 @@ def send(self, request, **kwargs):
6060
),
6161
origin=HttpxIntegration.origin,
6262
) as span:
63-
span.set_data(SPANDATA.HTTP_METHOD, request.method)
63+
data = {
64+
SPANDATA.HTTP_METHOD: request.method,
65+
}
6466
if parsed_url is not None:
65-
span.set_data("url", parsed_url.url)
66-
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
67-
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
67+
data["url"] = parsed_url.url
68+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
69+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
70+
71+
for key, value in data.items():
72+
span.set_data(key, value)
6873

6974
if should_propagate_trace(sentry_sdk.get_client(), str(request.url)):
7075
for (
@@ -89,6 +94,15 @@ def send(self, request, **kwargs):
8994
span.set_http_status(rv.status_code)
9095
span.set_data("reason", rv.reason_phrase)
9196

97+
data[SPANDATA.HTTP_STATUS_CODE] = rv.status_code
98+
data["reason"] = rv.reason_phrase
99+
100+
sentry_sdk.add_breadcrumb(
101+
type="http",
102+
category="httplib",
103+
data=data,
104+
)
105+
92106
return rv
93107

94108
Client.send = send
@@ -116,11 +130,16 @@ async def send(self, request, **kwargs):
116130
),
117131
origin=HttpxIntegration.origin,
118132
) as span:
119-
span.set_data(SPANDATA.HTTP_METHOD, request.method)
133+
data = {
134+
SPANDATA.HTTP_METHOD: request.method,
135+
}
120136
if parsed_url is not None:
121-
span.set_data("url", parsed_url.url)
122-
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
123-
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
137+
data["url"] = parsed_url.url
138+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
139+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
140+
141+
for key, value in data.items():
142+
span.set_data(key, value)
124143

125144
if should_propagate_trace(sentry_sdk.get_client(), str(request.url)):
126145
for (
@@ -145,6 +164,15 @@ async def send(self, request, **kwargs):
145164
span.set_http_status(rv.status_code)
146165
span.set_data("reason", rv.reason_phrase)
147166

167+
data[SPANDATA.HTTP_STATUS_CODE] = rv.status_code
168+
data["reason"] = rv.reason_phrase
169+
170+
sentry_sdk.add_breadcrumb(
171+
type="http",
172+
category="httplib",
173+
data=data,
174+
)
175+
148176
return rv
149177

150178
AsyncClient.send = send

sentry_sdk/integrations/redis/_async_common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
_get_pipeline_data,
1313
_update_span,
1414
)
15-
from sentry_sdk.tracing import Span
1615
from sentry_sdk.utils import capture_internal_exceptions
1716

1817
from typing import TYPE_CHECKING

sentry_sdk/integrations/redis/_sync_common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
_get_pipeline_data,
1313
_update_span,
1414
)
15-
from sentry_sdk.tracing import Span
1615
from sentry_sdk.utils import capture_internal_exceptions
1716

1817
from typing import TYPE_CHECKING

sentry_sdk/integrations/redis/modules/caches.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
if TYPE_CHECKING:
1515
from sentry_sdk.integrations.redis import RedisIntegration
16-
from sentry_sdk.tracing import Span
1716
from typing import Any, Optional
1817

1918

sentry_sdk/integrations/redis/modules/queries.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
if TYPE_CHECKING:
1212
from redis import Redis
1313
from sentry_sdk.integrations.redis import RedisIntegration
14-
from sentry_sdk.tracing import Span
1514
from typing import Any
1615

1716

sentry_sdk/integrations/redis/redis_cluster.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
RedisCluster as AsyncRedisCluster,
2424
ClusterPipeline as AsyncClusterPipeline,
2525
)
26-
from sentry_sdk.tracing import Span
2726

2827

2928
def _get_async_cluster_db_data(async_redis_cluster_instance):

sentry_sdk/integrations/stdlib.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,17 @@ def putrequest(self, method, url, *args, **kwargs):
9595
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
9696
origin="auto.http.stdlib.httplib",
9797
)
98-
span.set_data(SPANDATA.HTTP_METHOD, method)
98+
99+
data = {
100+
SPANDATA.HTTP_METHOD: method,
101+
}
99102
if parsed_url is not None:
100-
span.set_data("url", parsed_url.url)
101-
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
102-
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
103+
data["url"] = parsed_url.url
104+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
105+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
106+
107+
for key, value in data.items():
108+
span.set_data(key, value)
103109

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

@@ -118,6 +124,7 @@ def putrequest(self, method, url, *args, **kwargs):
118124
self.putheader(key, value)
119125

120126
self._sentrysdk_span = span # type: ignore[attr-defined]
127+
self._sentrysdk_span_data = data # type: ignore[attr-defined]
121128

122129
return rv
123130

@@ -130,6 +137,16 @@ def getresponse(self, *args, **kwargs):
130137

131138
rv = real_getresponse(self, *args, **kwargs)
132139

140+
span_data = getattr(self, "_sentrysdk_span_data", {})
141+
span_data[SPANDATA.HTTP_STATUS_CODE] = int(rv.status)
142+
span_data["reason"] = rv.reason
143+
144+
sentry_sdk.add_breadcrumb(
145+
type="http",
146+
category="httplib",
147+
data=span_data,
148+
)
149+
133150
span.set_http_status(int(rv.status))
134151
span.set_data("reason", rv.reason)
135152
span.finish()

sentry_sdk/tracing_utils.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,8 @@ def record_sql_queries(
157157

158158
def maybe_create_breadcrumbs_from_span(scope, span):
159159
# type: (sentry_sdk.Scope, sentry_sdk.tracing.Span) -> None
160-
if span.op == OP.HTTP_CLIENT:
161-
scope.add_breadcrumb(
162-
type="http",
163-
category="httplib",
164-
data=span._data,
165-
)
160+
# TODO: can be removed when POtelSpan replaces Span
161+
pass
166162

167163

168164
def _get_frame_module_abs_path(frame):

0 commit comments

Comments
 (0)