Skip to content

Commit 089daeb

Browse files
authored
Log HTTP errors responses of Jira client (#503)
* Log HTTP errors of Jira client * Lint * Override raise_for_status() instead of decorating each method * Unused import * Fix assertions about logs now that backoff is involved
1 parent a38e935 commit 089daeb

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed

jbi/services/jira.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from functools import lru_cache
1111
from typing import TYPE_CHECKING, Any
1212

13+
import requests
1314
from atlassian import Jira
1415
from atlassian import errors as atlassian_errors
1516
from requests import exceptions as requests_exceptions
@@ -39,10 +40,31 @@
3940

4041

4142
class JiraClient(Jira):
42-
"""Adapted Atlassian Jira client that wraps methods in our instrumentation
43-
decorator.
43+
"""Adapted Atlassian Jira client that logs errors and wraps methods
44+
in our instrumentation decorator.
4445
"""
4546

47+
def raise_for_status(self, *args, **kwargs):
48+
"""Catch and log HTTP errors responses of the Jira client.
49+
50+
Without this the actual requests and responses are not exposed when an error
51+
occurs, which makes troubleshooting tedious.
52+
"""
53+
try:
54+
return super().raise_for_status(*args, **kwargs)
55+
except requests.HTTPError as exc:
56+
request = exc.request
57+
response = exc.response
58+
logger.error(
59+
"HTTP: %s %s -> %s %s",
60+
request.method,
61+
request.path_url,
62+
response.status_code,
63+
response.reason,
64+
extra={"body": response.text},
65+
)
66+
raise
67+
4668
get_server_info = instrumented_method(Jira.get_server_info)
4769
get_permissions = instrumented_method(Jira.get_permissions)
4870
get_project_components = instrumented_method(Jira.get_project_components)

tests/unit/services/test_jira.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
2+
import logging
23

34
import pytest
5+
import requests
46
import responses
57
from requests.exceptions import ConnectionError
68

@@ -52,6 +54,39 @@ def test_jira_create_issue_is_instrumented(
5254
mocked_statsd.timer.assert_called_with("jbi.jira.methods.create_issue.timer")
5355

5456

57+
@pytest.mark.no_mocked_jira
58+
def test_jira_calls_log_http_errors(mocked_responses, context_create_example, caplog):
59+
url = f"{get_settings().jira_base_url}rest/api/2/project/{context_create_example.jira.project}/components"
60+
mocked_responses.add(
61+
responses.GET,
62+
url,
63+
status=404,
64+
json={
65+
"errorMessages": ["No project could be found with key 'X'."],
66+
"errors": {},
67+
},
68+
)
69+
70+
with caplog.at_level(logging.ERROR):
71+
with pytest.raises(requests.HTTPError):
72+
jira.create_jira_issue(
73+
context_create_example,
74+
"Description",
75+
sync_whiteboard_labels=False,
76+
components=["Remote Settings"],
77+
)
78+
79+
log_messages = [log.msg % log.args for log in caplog.records]
80+
idx = log_messages.index(
81+
"HTTP: GET /rest/api/2/project/JBI/components -> 404 Not Found"
82+
)
83+
log_record = caplog.records[idx]
84+
assert (
85+
log_record.body
86+
== '{"errorMessages": ["No project could be found with key \'X\'."], "errors": {}}'
87+
)
88+
89+
5590
@pytest.mark.no_mocked_jira
5691
def test_create_issue_with_components(mocked_responses, context_create_example):
5792
url = f"{get_settings().jira_base_url}rest/api/2/project/{context_create_example.jira.project}/components"

0 commit comments

Comments
 (0)