Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tracking.py 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. from __future__ import absolute_import, unicode_literals
  2. import datetime
  3. import json
  4. from threading import local
  5. from time import time
  6. from django.utils import six
  7. from django.utils.encoding import force_text
  8. from debug_toolbar import settings as dt_settings
  9. from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace
  10. class SQLQueryTriggered(Exception):
  11. """Thrown when template panel triggers a query"""
  12. pass
  13. class ThreadLocalState(local):
  14. def __init__(self):
  15. self.enabled = True
  16. @property
  17. def Wrapper(self):
  18. if self.enabled:
  19. return NormalCursorWrapper
  20. return ExceptionCursorWrapper
  21. def recording(self, v):
  22. self.enabled = v
  23. state = ThreadLocalState()
  24. recording = state.recording # export function
  25. def wrap_cursor(connection, panel):
  26. if not hasattr(connection, '_djdt_cursor'):
  27. connection._djdt_cursor = connection.cursor
  28. def cursor(*args, **kwargs):
  29. # Per the DB API cursor() does not accept any arguments. There's
  30. # some code in the wild which does not follow that convention,
  31. # so we pass on the arguments even though it's not clean.
  32. # See:
  33. # https://github.com/jazzband/django-debug-toolbar/pull/615
  34. # https://github.com/jazzband/django-debug-toolbar/pull/896
  35. return state.Wrapper(connection._djdt_cursor(*args, **kwargs), connection, panel)
  36. connection.cursor = cursor
  37. return cursor
  38. def unwrap_cursor(connection):
  39. if hasattr(connection, '_djdt_cursor'):
  40. del connection._djdt_cursor
  41. del connection.cursor
  42. class ExceptionCursorWrapper(object):
  43. """
  44. Wraps a cursor and raises an exception on any operation.
  45. Used in Templates panel.
  46. """
  47. def __init__(self, cursor, db, logger):
  48. pass
  49. def __getattr__(self, attr):
  50. raise SQLQueryTriggered()
  51. class NormalCursorWrapper(object):
  52. """
  53. Wraps a cursor and logs queries.
  54. """
  55. def __init__(self, cursor, db, logger):
  56. self.cursor = cursor
  57. # Instance of a BaseDatabaseWrapper subclass
  58. self.db = db
  59. # logger must implement a ``record`` method
  60. self.logger = logger
  61. def _quote_expr(self, element):
  62. if isinstance(element, six.string_types):
  63. return "'%s'" % force_text(element).replace("'", "''")
  64. else:
  65. return repr(element)
  66. def _quote_params(self, params):
  67. if not params:
  68. return params
  69. if isinstance(params, dict):
  70. return {key: self._quote_expr(value) for key, value in params.items()}
  71. return [self._quote_expr(p) for p in params]
  72. def _decode(self, param):
  73. # If a sequence type, decode each element separately
  74. if isinstance(param, list) or isinstance(param, tuple):
  75. return [self._decode(element) for element in param]
  76. # If a dictionary type, decode each value separately
  77. if isinstance(param, dict):
  78. return {key: self._decode(value) for key, value in param.items()}
  79. # make sure datetime, date and time are converted to string by force_text
  80. CONVERT_TYPES = (datetime.datetime, datetime.date, datetime.time)
  81. try:
  82. return force_text(param, strings_only=not isinstance(param, CONVERT_TYPES))
  83. except UnicodeDecodeError:
  84. return '(encoded string)'
  85. def _record(self, method, sql, params):
  86. start_time = time()
  87. try:
  88. return method(sql, params)
  89. finally:
  90. stop_time = time()
  91. duration = (stop_time - start_time) * 1000
  92. if dt_settings.get_config()['ENABLE_STACKTRACES']:
  93. stacktrace = tidy_stacktrace(reversed(get_stack()))
  94. else:
  95. stacktrace = []
  96. _params = ''
  97. try:
  98. _params = json.dumps([self._decode(p) for p in params])
  99. except TypeError:
  100. pass # object not JSON serializable
  101. template_info = get_template_info()
  102. alias = getattr(self.db, 'alias', 'default')
  103. conn = self.db.connection
  104. vendor = getattr(conn, 'vendor', 'unknown')
  105. params = {
  106. 'vendor': vendor,
  107. 'alias': alias,
  108. 'sql': self.db.ops.last_executed_query(
  109. self.cursor, sql, self._quote_params(params)),
  110. 'duration': duration,
  111. 'raw_sql': sql,
  112. 'params': _params,
  113. 'raw_params': params,
  114. 'stacktrace': stacktrace,
  115. 'start_time': start_time,
  116. 'stop_time': stop_time,
  117. 'is_slow': duration > dt_settings.get_config()['SQL_WARNING_THRESHOLD'],
  118. 'is_select': sql.lower().strip().startswith('select'),
  119. 'template_info': template_info,
  120. }
  121. if vendor == 'postgresql':
  122. # If an erroneous query was ran on the connection, it might
  123. # be in a state where checking isolation_level raises an
  124. # exception.
  125. try:
  126. iso_level = conn.isolation_level
  127. except conn.InternalError:
  128. iso_level = 'unknown'
  129. params.update({
  130. 'trans_id': self.logger.get_transaction_id(alias),
  131. 'trans_status': conn.get_transaction_status(),
  132. 'iso_level': iso_level,
  133. 'encoding': conn.encoding,
  134. })
  135. # We keep `sql` to maintain backwards compatibility
  136. self.logger.record(**params)
  137. def callproc(self, procname, params=None):
  138. return self._record(self.cursor.callproc, procname, params)
  139. def execute(self, sql, params=None):
  140. return self._record(self.cursor.execute, sql, params)
  141. def executemany(self, sql, param_list):
  142. return self._record(self.cursor.executemany, sql, param_list)
  143. def __getattr__(self, attr):
  144. return getattr(self.cursor, attr)
  145. def __iter__(self):
  146. return iter(self.cursor)
  147. def __enter__(self):
  148. return self
  149. def __exit__(self, type, value, traceback):
  150. self.close()