33
44import sys
55import weakref
6- import contextlib
76
87from django import VERSION as DJANGO_VERSION # type: ignore
98from django .db .models .query import QuerySet # type: ignore
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
3733from sentry_sdk .hub import _should_send_default_pii
3834from sentry_sdk .scope import add_global_event_processor
3935from 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
4137from 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)
4943from sentry_sdk .integrations import Integration
5044from sentry_sdk .integrations .logging import ignore_logger
5145from sentry_sdk .integrations .wsgi import SentryWsgiMiddleware
5246from sentry_sdk .integrations ._wsgi_common import RequestExtractor
47+ from sentry_sdk .integrations ._sql_common import format_sql
5348from sentry_sdk .integrations .django .transactions import LEGACY_RESOLVER
5449from 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-
382295def 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
0 commit comments