diff --git a/talisker/config.py b/talisker/config.py index 919090b2..afc6ff7e 100644 --- a/talisker/config.py +++ b/talisker/config.py @@ -129,6 +129,7 @@ class Config(): 'TALISKER_NETWORKS': [], 'TALISKER_ID_HEADER': 'X-Request-Id', 'TALISKER_DEADLINE_HEADER': 'X-Request-Deadline', + 'TALISKER_EXPLAIN_SQL': False, } Metadata = collections.namedtuple( @@ -263,6 +264,16 @@ def slowquery_threshold(self, raw_name): """ return force_int(self[raw_name]) + @config_property('TALISKER_EXPLAIN_SQL') + def explain_sql(self, raw_name): + """Include EXPLAIN plans in sql sentry breadcrumbs. Defaults to false. + + When enabled, this will issue extra queries to the db when generating + sentry reports. The EXPLAIN queries will be quick to execute, as it + doesn't actually run the query, but still, caution is advised. + """ + return self.is_active(raw_name) + @config_property('TALISKER_SOFT_REQUEST_TIMEOUT') def soft_request_timeout(self, raw_name): """Set the threshold (in ms) over which WSGI requests will report a diff --git a/talisker/postgresql.py b/talisker/postgresql.py index 27f0c9fb..de068df6 100644 --- a/talisker/postgresql.py +++ b/talisker/postgresql.py @@ -71,6 +71,7 @@ def prettify_sql(sql): class TaliskerConnection(connection): _logger = None _threshold = None + _explain = None _safe_dsn = None _safe_dsn_format = '{user}@{host}:{port}/{dbname}' @@ -99,6 +100,12 @@ def query_threshold(self): self._threshold = talisker.get_config().slowquery_threshold return self._threshold + @property + def explain_breadcrumbs(self): + if self._explain is None: + self._explain = talisker.get_config().explain_sql + return self._explain + def cursor(self, *args, **kwargs): kwargs.setdefault('cursor_factory', TaliskerCursor) return super().cursor(*args, **kwargs) @@ -129,13 +136,14 @@ def _record(self, msg, query, vars, duration, extra={}): def processor(data): qdata['query'] = self._format_query(query, vars) - try: - cursor = base_connection.cursor() - cursor.execute('EXPLAIN ' + query, vars) - plan = '\n'.join(l[0] for l in cursor.fetchall()) - qdata['plan'] = plan - except Exception as e: - qdata['plan'] = 'could not explain query: ' + str(e) + if self.explain_breadcrumbs: + try: + cursor = base_connection.cursor() + cursor.execute('EXPLAIN ' + query, vars) + plan = '\n'.join(l[0] for l in cursor.fetchall()) + qdata['plan'] = plan + except Exception as e: + qdata['plan'] = 'could not explain query: ' + str(e) data['data'].update(qdata) diff --git a/tests/test_config.py b/tests/test_config.py index 15b756c2..a36fa651 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -61,6 +61,7 @@ def test_config_defaults(): debuglog=None, colour=False, slowquery_threshold=-1, + explain_sql=False, soft_request_timeout=-1, request_timeout=None, logstatus=False, @@ -127,6 +128,11 @@ def test_query_threshold_config(): assert msg == "'garbage' is not a valid integer" +def test_explain_sql_config(): + assert_config({'TALISKER_EXPLAIN_SQL': '1'}, explain_sql=True) + assert_config({'TALISKER_EXPLAIN_SQL': 'garbage'}, explain_sql=False) + + def test_request_timeout_config(): assert_config( {'TALISKER_SOFT_REQUEST_TIMEOUT': '3000'}, soft_request_timeout=3000) diff --git a/tests/test_sentry.py b/tests/test_sentry.py index 0a96d827..08661d3f 100644 --- a/tests/test_sentry.py +++ b/tests/test_sentry.py @@ -301,10 +301,11 @@ def test_sql_summary_crumb(): @require_module('psycopg2') -def test_sql_crumbs_functional(request, postgresql, context): +def test_sql_crumbs_functional(request, postgresql, context, config): import psycopg2 from talisker.postgresql import TaliskerConnection + config['TALISKER_EXPLAIN_SQL'] = '1' table = request.function.__name__ with psycopg2.connect(postgresql.dsn) as ddl: