Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e23114c

Browse filesBrowse files
authored
fix: remove query text from exception message, use exception.debug_message instead (#1105)
Since query text can potentially contain sensitive information, remove it from the default exception message. This information is useful for debugging, so provide a `debug_message` attribute, which is not included in the exception representation (and thus the logs). Fixes internal issue 211616590
1 parent 18ee0b7 commit e23114c
Copy full SHA for e23114c

File tree

2 files changed

+29
-10
lines changed
Filter options

2 files changed

+29
-10
lines changed

‎google/cloud/bigquery/job/query.py

Copy file name to clipboardExpand all lines: google/cloud/bigquery/job/query.py
+13-6Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666

6767

6868
_CONTAINS_ORDER_BY = re.compile(r"ORDER\s+BY", re.IGNORECASE)
69+
_EXCEPTION_FOOTER_TEMPLATE = "{message}\n\nLocation: {location}\nJob ID: {job_id}\n"
6970
_TIMEOUT_BUFFER_SECS = 0.1
7071

7172

@@ -1196,17 +1197,17 @@ def _blocking_poll(self, timeout=None, **kwargs):
11961197
super(QueryJob, self)._blocking_poll(timeout=timeout, **kwargs)
11971198

11981199
@staticmethod
1199-
def _format_for_exception(query, job_id):
1200+
def _format_for_exception(message: str, query: str):
12001201
"""Format a query for the output in exception message.
12011202
12021203
Args:
1204+
message (str): The original exception message.
12031205
query (str): The SQL query to format.
1204-
job_id (str): The ID of the job that ran the query.
12051206
12061207
Returns:
12071208
str: A formatted query text.
12081209
"""
1209-
template = "\n\n(job ID: {job_id})\n\n{header}\n\n{ruler}\n{body}\n{ruler}"
1210+
template = "{message}\n\n{header}\n\n{ruler}\n{body}\n{ruler}"
12101211

12111212
lines = query.splitlines()
12121213
max_line_len = max(len(line) for line in lines)
@@ -1223,7 +1224,7 @@ def _format_for_exception(query, job_id):
12231224
"{:4}:{}".format(n, line) for n, line in enumerate(lines, start=1)
12241225
)
12251226

1226-
return template.format(job_id=job_id, header=header, ruler=ruler, body=body)
1227+
return template.format(message=message, header=header, ruler=ruler, body=body)
12271228

12281229
def _begin(self, client=None, retry=DEFAULT_RETRY, timeout=None):
12291230
"""API call: begin the job via a POST request
@@ -1248,7 +1249,10 @@ def _begin(self, client=None, retry=DEFAULT_RETRY, timeout=None):
12481249
try:
12491250
super(QueryJob, self)._begin(client=client, retry=retry, timeout=timeout)
12501251
except exceptions.GoogleAPICallError as exc:
1251-
exc.message += self._format_for_exception(self.query, self.job_id)
1252+
exc.message = _EXCEPTION_FOOTER_TEMPLATE.format(
1253+
message=exc.message, location=self.location, job_id=self.job_id
1254+
)
1255+
exc.debug_message = self._format_for_exception(exc.message, self.query)
12521256
exc.query_job = self
12531257
raise
12541258

@@ -1447,7 +1451,10 @@ def do_get_result():
14471451
do_get_result()
14481452

14491453
except exceptions.GoogleAPICallError as exc:
1450-
exc.message += self._format_for_exception(self.query, self.job_id)
1454+
exc.message = _EXCEPTION_FOOTER_TEMPLATE.format(
1455+
message=exc.message, location=self.location, job_id=self.job_id
1456+
)
1457+
exc.debug_message = self._format_for_exception(exc.message, self.query) # type: ignore
14511458
exc.query_job = self # type: ignore
14521459
raise
14531460
except requests.exceptions.Timeout as exc:

‎tests/unit/job/test_query.py

Copy file name to clipboardExpand all lines: tests/unit/job/test_query.py
+16-4Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,13 +1360,19 @@ def test_result_error(self):
13601360
exc_job_instance = getattr(exc_info.exception, "query_job", None)
13611361
self.assertIs(exc_job_instance, job)
13621362

1363+
# Query text could contain sensitive information, so it must not be
1364+
# included in logs / exception representation.
13631365
full_text = str(exc_info.exception)
13641366
assert job.job_id in full_text
1365-
assert "Query Job SQL Follows" in full_text
1367+
assert "Query Job SQL Follows" not in full_text
13661368

1369+
# It is useful to have query text available, so it is provided in a
1370+
# debug_message property.
1371+
debug_message = exc_info.exception.debug_message
1372+
assert "Query Job SQL Follows" in debug_message
13671373
for i, line in enumerate(query.splitlines(), start=1):
13681374
expected_line = "{}:{}".format(i, line)
1369-
assert expected_line in full_text
1375+
assert expected_line in debug_message
13701376

13711377
def test_result_transport_timeout_error(self):
13721378
query = textwrap.dedent(
@@ -1452,13 +1458,19 @@ def test__begin_error(self):
14521458
exc_job_instance = getattr(exc_info.exception, "query_job", None)
14531459
self.assertIs(exc_job_instance, job)
14541460

1461+
# Query text could contain sensitive information, so it must not be
1462+
# included in logs / exception representation.
14551463
full_text = str(exc_info.exception)
14561464
assert job.job_id in full_text
1457-
assert "Query Job SQL Follows" in full_text
1465+
assert "Query Job SQL Follows" not in full_text
14581466

1467+
# It is useful to have query text available, so it is provided in a
1468+
# debug_message property.
1469+
debug_message = exc_info.exception.debug_message
1470+
assert "Query Job SQL Follows" in debug_message
14591471
for i, line in enumerate(query.splitlines(), start=1):
14601472
expected_line = "{}:{}".format(i, line)
1461-
assert expected_line in full_text
1473+
assert expected_line in debug_message
14621474

14631475
def test__begin_w_timeout(self):
14641476
PATH = "/projects/%s/jobs" % (self.PROJECT,)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.