Skip to content

Commit 322a8f4

Browse files
committed
ref: Add layer to abstract breadcrumb vs spans
1 parent 6d91f3f commit 322a8f4

3 files changed

Lines changed: 85 additions & 70 deletions

File tree

sentry_sdk/integrations/django/__init__.py

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from sentry_sdk.hub import _should_send_default_pii
3838
from sentry_sdk.scope import add_global_event_processor
3939
from sentry_sdk.serializer import add_global_repr_processor
40+
from sentry_sdk.tracing import record_sql_query
4041
from sentry_sdk.utils import (
4142
capture_internal_exceptions,
4243
event_from_exception,
@@ -331,61 +332,49 @@ def format_sql(sql, params):
331332
return sql, rv
332333

333334

334-
@contextlib.contextmanager
335-
def record_sql(sql, params, cursor=None):
335+
def record_sql(sql, param_list, cursor=None):
336336
# type: (Any, Any, Any) -> Generator
337337
hub = Hub.current
338338
if hub.get_integration(DjangoIntegration) is None:
339339
yield
340340
return
341341

342-
real_sql = None
343-
real_params = None
342+
formatted_queries = []
344343

345-
try:
346-
# Prefer our own SQL formatting logic because it's the only one that
347-
# has proper value trimming.
348-
real_sql, real_params = format_sql(sql, params)
349-
if real_sql:
350-
real_sql = format_and_strip(real_sql, real_params)
351-
except Exception:
352-
pass
344+
for params in param_list:
345+
real_sql = None
346+
real_params = None
353347

354-
if not real_sql and cursor and hasattr(cursor, "mogrify"):
355-
# If formatting failed and we're using psycopg2, it could be that we're
356-
# looking at a query that uses Composed objects. Use psycopg2's mogrify
357-
# function to format the query. We lose per-parameter trimming but gain
358-
# accuracy in formatting.
359-
#
360-
# This is intentionally the second choice because we assume Composed
361-
# queries are not widely used, while per-parameter trimming is
362-
# generally highly desirable.
363348
try:
364-
if cursor and hasattr(cursor, "mogrify"):
365-
real_sql = cursor.mogrify(sql, params)
366-
if isinstance(real_sql, bytes):
367-
real_sql = real_sql.decode(cursor.connection.encoding)
349+
# Prefer our own SQL formatting logic because it's the only one that
350+
# has proper value trimming.
351+
real_sql, real_params = format_sql(sql, params)
352+
if real_sql:
353+
real_sql = format_and_strip(real_sql, real_params)
368354
except Exception:
369355
pass
370356

371-
if real_sql:
372-
hub.add_breadcrumb(message=real_sql, category="query")
373-
with hub.span(op="db.statement", description=real_sql):
374-
yield
375-
else:
376-
yield
357+
if not real_sql and cursor and hasattr(cursor, "mogrify"):
358+
# If formatting failed and we're using psycopg2, it could be that we're
359+
# looking at a query that uses Composed objects. Use psycopg2's mogrify
360+
# function to format the query. We lose per-parameter trimming but gain
361+
# accuracy in formatting.
362+
#
363+
# This is intentionally the second choice because we assume Composed
364+
# queries are not widely used, while per-parameter trimming is
365+
# generally highly desirable.
366+
try:
367+
if cursor and hasattr(cursor, "mogrify"):
368+
real_sql = cursor.mogrify(sql, params)
369+
if isinstance(real_sql, bytes):
370+
real_sql = real_sql.decode(cursor.connection.encoding)
371+
except Exception:
372+
pass
377373

374+
if real_sql:
375+
formatted_queries.append(real_sql)
378376

379-
@contextlib.contextmanager
380-
def record_many_sql(sql, param_list, cursor):
381-
ctxs = [record_sql(sql, params, cursor).__enter__() for params in param_list]
382-
383-
try:
384-
yield
385-
finally:
386-
einfo = sys.exc_info()
387-
for ctx in ctxs:
388-
ctx.__exit__(*einfo)
377+
return record_sql_queries(hub, formatted_queries)
389378

390379

391380
def install_sql_hook():
@@ -404,7 +393,7 @@ def install_sql_hook():
404393
return
405394

406395
def execute(self, sql, params=None):
407-
with record_sql(sql, params, self.cursor):
396+
with record_sql(sql, [params], self.cursor):
408397
return real_execute(self, sql, params)
409398

410399
def executemany(self, sql, param_list):

sentry_sdk/integrations/stdlib.py

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from sentry_sdk.hub import Hub
22
from sentry_sdk.integrations import Integration
3+
from sentry_sdk.tracing import record_http_request
34

45

56
try:
@@ -23,10 +24,9 @@ def install_httplib():
2324
real_getresponse = HTTPConnection.getresponse
2425

2526
def putrequest(self, method, url, *args, **kwargs):
26-
rv = real_putrequest(self, method, url, *args, **kwargs)
2727
hub = Hub.current
2828
if hub.get_integration(StdlibIntegration) is None:
29-
return rv
29+
return real_putrequest(self, method, url, *args, **kwargs)
3030

3131
host = self.host
3232
port = self.port
@@ -41,40 +41,37 @@ def putrequest(self, method, url, *args, **kwargs):
4141
url,
4242
)
4343

44-
self._sentrysdk_data_dict = data = {}
45-
self._sentrysdk_span = hub.start_span(
46-
op="http", description="%s %s" % (real_url, method)
47-
)
44+
self._sentrysdk_recorder = record_http_request(hub, real_url, method)
45+
self._sentrysdk_data_dict = self._sentrysdk_recorder.__enter__()
4846

49-
for key, value in hub.iter_trace_propagation_headers():
50-
self.putheader(key, value)
47+
try:
48+
rv = real_putrequest(self, method, url, *args, **kwargs)
49+
50+
for key, value in hub.iter_trace_propagation_headers():
51+
self.putheader(key, value)
52+
except Exception:
53+
self._sentrysdk_recorder.__exit__(*sys.exc_info())
54+
self._sentrysdk_recorder = self._sentrysdk_data_dict = None
55+
raise
5156

52-
data["url"] = real_url
53-
data["method"] = method
5457
return rv
5558

5659
def getresponse(self, *args, **kwargs):
57-
rv = real_getresponse(self, *args, **kwargs)
58-
hub = Hub.current
59-
if hub.get_integration(StdlibIntegration) is None:
60-
return rv
61-
62-
data = getattr(self, "_sentrysdk_data_dict", None) or {}
60+
recorder = getattr(self, "_sentrysdk_recorder", None)
61+
data_dict = getattr(self, "_sentrysdk_data_dict", None)
6362

64-
if "status_code" not in data:
65-
data["status_code"] = rv.status
66-
data["reason"] = rv.reason
63+
try:
64+
rv = real_getresponse(self, *args, **kwargs)
6765

68-
span = self._sentrysdk_span
69-
if span is not None:
70-
span.set_tag("status_code", rv.status)
71-
for k, v in data.items():
72-
span.set_data(k, v)
73-
span.finish()
66+
if recorder is not None and data_dict is not None:
67+
data_dict["httplib_response"] = rv
68+
data_dict["status_code"] = rv.status
69+
data_dict["reason"] = rv.reason
70+
finally:
71+
if recorder is not None:
72+
recorder.__exit__(*sys.exc_info())
73+
self._sentrysdk_recorder = self._sentrysdk_data_dict = None
7474

75-
hub.add_breadcrumb(
76-
type="http", category="httplib", data=data, hint={"httplib_response": rv}
77-
)
7875
return rv
7976

8077
HTTPConnection.putrequest = putrequest

sentry_sdk/tracing.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
import uuid
3+
import contextlib
34

45
from datetime import datetime
56

@@ -155,3 +156,31 @@ def to_json(self):
155156

156157
def get_trace_context(self):
157158
return {"trace_id": self.trace_id, "span_id": self.span_id}
159+
160+
161+
@contextlib.contextmanager
162+
def record_sql_query(hub, queries):
163+
if not queries:
164+
yield None
165+
else:
166+
for query in queries:
167+
hub.add_breadcrumb(message=query, category="query")
168+
169+
description = ";".join(queries)
170+
with hub.span(op="db.statement", description=description) as span:
171+
yield span
172+
173+
174+
@contextlib.contextmanager
175+
def record_http_request(hub, url, method):
176+
data_dict = {"url": url, "method": method}
177+
178+
with hub.span(op="http", description="%s %s" % (url, method)) as span:
179+
try:
180+
yield data_dict
181+
finally:
182+
httplib_response = data_dict.pop("httplib_response", None)
183+
if "status_code" in data_dict:
184+
span.set_tag("http.status_code", data_dict["status_code"])
185+
for k, v in data_dict.items():
186+
span.set_data(k, v)

0 commit comments

Comments
 (0)