Skip to content

Commit cc68a77

Browse files
committed
ref: Move SQL instrumentation out of Django
1 parent f239d12 commit cc68a77

4 files changed

Lines changed: 101 additions & 98 deletions

File tree

examples/tracing/tracing.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,11 @@ def decode_base64(encoded, redis_key):
3939

4040
@app.route("/")
4141
def index():
42-
with sentry_sdk.configure_scope() as scope:
43-
return flask.render_template(
44-
"index.html",
45-
sentry_dsn=os.environ["SENTRY_DSN"],
46-
traceparent=dict(sentry_sdk.Hub.current.iter_trace_propagation_headers()),
47-
)
42+
return flask.render_template(
43+
"index.html",
44+
sentry_dsn=os.environ["SENTRY_DSN"],
45+
traceparent=dict(sentry_sdk.Hub.current.iter_trace_propagation_headers()),
46+
)
4847

4948

5049
@app.route("/compute/<input>")
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import
3+
4+
from sentry_sdk.utils import format_and_strip, safe_repr
5+
6+
if False:
7+
from typing import Any
8+
from typing import Dict
9+
from typing import List
10+
from typing import Tuple
11+
from typing import Optional
12+
13+
14+
class _FormatConverter(object):
15+
def __init__(self, param_mapping):
16+
# type: (Dict[str, int]) -> None
17+
18+
self.param_mapping = param_mapping
19+
self.params = [] # type: List[Any]
20+
21+
def __getitem__(self, val):
22+
# type: (str) -> str
23+
self.params.append(self.param_mapping.get(val))
24+
return "%s"
25+
26+
27+
def _format_sql_impl(sql, params):
28+
# type: (Any, Any) -> Tuple[str, List[str]]
29+
rv = []
30+
31+
if isinstance(params, dict):
32+
# convert sql with named parameters to sql with unnamed parameters
33+
conv = _FormatConverter(params)
34+
if params:
35+
sql = sql % conv
36+
params = conv.params
37+
else:
38+
params = ()
39+
40+
for param in params or ():
41+
if param is None:
42+
rv.append("NULL")
43+
param = safe_repr(param)
44+
rv.append(param)
45+
46+
return sql, rv
47+
48+
49+
def format_sql(sql, params, cursor):
50+
# type: (str, List[Any], Any) -> Optional[str]
51+
52+
real_sql = None
53+
real_params = None
54+
55+
try:
56+
# Prefer our own SQL formatting logic because it's the only one that
57+
# has proper value trimming.
58+
real_sql, real_params = _format_sql_impl(sql, params)
59+
if real_sql:
60+
real_sql = format_and_strip(real_sql, real_params)
61+
except Exception:
62+
pass
63+
64+
if not real_sql and cursor and hasattr(cursor, "mogrify"):
65+
# If formatting failed and we're using psycopg2, it could be that we're
66+
# looking at a query that uses Composed objects. Use psycopg2's mogrify
67+
# function to format the query. We lose per-parameter trimming but gain
68+
# accuracy in formatting.
69+
#
70+
# This is intentionally the second choice because we assume Composed
71+
# queries are not widely used, while per-parameter trimming is
72+
# generally highly desirable.
73+
try:
74+
if cursor and hasattr(cursor, "mogrify"):
75+
real_sql = cursor.mogrify(sql, params)
76+
if isinstance(real_sql, bytes):
77+
real_sql = real_sql.decode(cursor.connection.encoding)
78+
except Exception:
79+
pass
80+
81+
return real_sql or None

sentry_sdk/integrations/django/__init__.py

Lines changed: 14 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import sys
55
import weakref
6-
import contextlib
76

87
from django import VERSION as DJANGO_VERSION # type: ignore
98
from django.db.models.query import QuerySet # type: ignore
@@ -13,11 +12,8 @@
1312
from typing import Any
1413
from typing import Callable
1514
from typing import Dict
16-
from typing import List
1715
from typing import Optional
18-
from typing import Tuple
1916
from typing import Union
20-
from typing import Generator
2117

2218
from django.core.handlers.wsgi import WSGIRequest # type: ignore
2319
from django.http.response import HttpResponse # type: ignore
@@ -37,19 +33,18 @@
3733
from sentry_sdk.hub import _should_send_default_pii
3834
from sentry_sdk.scope import add_global_event_processor
3935
from sentry_sdk.serializer import add_global_repr_processor
40-
from sentry_sdk.tracing import record_sql_query
36+
from sentry_sdk.tracing import record_sql_queries
4137
from sentry_sdk.utils import (
4238
capture_internal_exceptions,
4339
event_from_exception,
44-
safe_repr,
45-
format_and_strip,
4640
transaction_from_function,
4741
walk_exception_chain,
4842
)
4943
from sentry_sdk.integrations import Integration
5044
from sentry_sdk.integrations.logging import ignore_logger
5145
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
5246
from sentry_sdk.integrations._wsgi_common import RequestExtractor
47+
from sentry_sdk.integrations._sql_common import format_sql
5348
from sentry_sdk.integrations.django.transactions import LEGACY_RESOLVER
5449
from sentry_sdk.integrations.django.templates import get_template_frame_from_exception
5550

@@ -297,88 +292,6 @@ def _set_user_info(request, event):
297292
pass
298293

299294

300-
class _FormatConverter(object):
301-
def __init__(self, param_mapping):
302-
# type: (Dict[str, int]) -> None
303-
304-
self.param_mapping = param_mapping
305-
self.params = [] # type: List[Any]
306-
307-
def __getitem__(self, val):
308-
# type: (str) -> str
309-
self.params.append(self.param_mapping.get(val))
310-
return "%s"
311-
312-
313-
def format_sql(sql, params):
314-
# type: (Any, Any) -> Tuple[str, List[str]]
315-
rv = []
316-
317-
if isinstance(params, dict):
318-
# convert sql with named parameters to sql with unnamed parameters
319-
conv = _FormatConverter(params)
320-
if params:
321-
sql = sql % conv
322-
params = conv.params
323-
else:
324-
params = ()
325-
326-
for param in params or ():
327-
if param is None:
328-
rv.append("NULL")
329-
param = safe_repr(param)
330-
rv.append(param)
331-
332-
return sql, rv
333-
334-
335-
@contextlib.contextmanager
336-
def record_sql(sql, param_list, cursor=None):
337-
# type: (Any, Any, Any) -> Generator
338-
hub = Hub.current
339-
if hub.get_integration(DjangoIntegration) is None:
340-
yield None
341-
return
342-
343-
formatted_queries = []
344-
345-
for params in param_list:
346-
real_sql = None
347-
real_params = None
348-
349-
try:
350-
# Prefer our own SQL formatting logic because it's the only one that
351-
# has proper value trimming.
352-
real_sql, real_params = format_sql(sql, params)
353-
if real_sql:
354-
real_sql = format_and_strip(real_sql, real_params)
355-
except Exception:
356-
pass
357-
358-
if not real_sql and cursor and hasattr(cursor, "mogrify"):
359-
# If formatting failed and we're using psycopg2, it could be that we're
360-
# looking at a query that uses Composed objects. Use psycopg2's mogrify
361-
# function to format the query. We lose per-parameter trimming but gain
362-
# accuracy in formatting.
363-
#
364-
# This is intentionally the second choice because we assume Composed
365-
# queries are not widely used, while per-parameter trimming is
366-
# generally highly desirable.
367-
try:
368-
if cursor and hasattr(cursor, "mogrify"):
369-
real_sql = cursor.mogrify(sql, params)
370-
if isinstance(real_sql, bytes):
371-
real_sql = real_sql.decode(cursor.connection.encoding)
372-
except Exception:
373-
pass
374-
375-
if real_sql:
376-
formatted_queries.append(real_sql)
377-
378-
with record_sql_query(hub, formatted_queries):
379-
yield
380-
381-
382295
def install_sql_hook():
383296
# type: () -> None
384297
"""If installed this causes Django's queries to be captured."""
@@ -395,11 +308,21 @@ def install_sql_hook():
395308
return
396309

397310
def execute(self, sql, params=None):
398-
with record_sql(sql, [params], self.cursor):
311+
hub = Hub.current
312+
if hub.get_integration(DjangoIntegration) is None:
313+
return
314+
315+
with record_sql_queries(hub, [format_sql(sql, params, self.cursor)]):
399316
return real_execute(self, sql, params)
400317

401318
def executemany(self, sql, param_list):
402-
with record_sql(sql, param_list, self.cursor):
319+
hub = Hub.current
320+
if hub.get_integration(DjangoIntegration) is None:
321+
return
322+
323+
with record_sql_queries(
324+
hub, [format_sql(sql, params, self.cursor) for params in param_list]
325+
):
403326
return real_executemany(self, sql, param_list)
404327

405328
CursorWrapper.execute = execute

sentry_sdk/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def get_trace_context(self):
190190

191191

192192
@contextlib.contextmanager
193-
def record_sql_query(hub, queries):
193+
def record_sql_queries(hub, queries):
194194
if not queries:
195195
yield None
196196
else:

0 commit comments

Comments
 (0)