Files
edx-platform/lms/lib/comment_client/utils.py
Greg Price dc2342859e Improve handling of bad JSON from comments service
Instead of allowing the JSONDecodeError to escape, raise an error
indicating the request id and containing the first 100 characters
of the response to aid investigation.
2014-06-03 13:34:36 -04:00

155 lines
4.5 KiB
Python

from contextlib import contextmanager
from dogapi import dog_stats_api
import logging
import requests
from django.conf import settings
from time import time
from uuid import uuid4
from django.utils.translation import get_language
log = logging.getLogger(__name__)
def strip_none(dic):
return dict([(k, v) for k, v in dic.iteritems() if v is not None])
def strip_blank(dic):
def _is_blank(v):
return isinstance(v, str) and len(v.strip()) == 0
return dict([(k, v) for k, v in dic.iteritems() if not _is_blank(v)])
def extract(dic, keys):
if isinstance(keys, str):
return strip_none({keys: dic.get(keys)})
else:
return strip_none({k: dic.get(k) for k in keys})
def merge_dict(dic1, dic2):
return dict(dic1.items() + dic2.items())
@contextmanager
def request_timer(request_id, method, url, tags=None):
start = time()
with dog_stats_api.timer('comment_client.request.time', tags=tags):
yield
end = time()
duration = end - start
log.info(
"comment_client_request_log: request_id={request_id}, method={method}, "
"url={url}, duration={duration}".format(
request_id=request_id,
method=method,
url=url,
duration=duration
)
)
def perform_request(method, url, data_or_params=None, raw=False,
metric_action=None, metric_tags=None, paged_results=False):
if metric_tags is None:
metric_tags = []
metric_tags.append(u'method:{}'.format(method))
if metric_action:
metric_tags.append(u'action:{}'.format(metric_action))
if data_or_params is None:
data_or_params = {}
headers = {
'X-Edx-Api-Key': getattr(settings, "COMMENTS_SERVICE_KEY", None),
'Accept-Language': get_language(),
}
request_id = uuid4()
request_id_dict = {'request_id': request_id}
if method in ['post', 'put', 'patch']:
data = data_or_params
params = request_id_dict
else:
data = None
params = merge_dict(data_or_params, request_id_dict)
with request_timer(request_id, method, url, metric_tags):
response = requests.request(
method,
url,
data=data,
params=params,
headers=headers,
timeout=5
)
metric_tags.append(u'status_code:{}'.format(response.status_code))
if response.status_code > 200:
metric_tags.append(u'result:failure')
else:
metric_tags.append(u'result:success')
dog_stats_api.increment('comment_client.request.count', tags=metric_tags)
if 200 < response.status_code < 500:
raise CommentClientRequestError(response.text, response.status_code)
# Heroku returns a 503 when an application is in maintenance mode
elif response.status_code == 503:
raise CommentClientMaintenanceError(response.text)
elif response.status_code == 500:
raise CommentClient500Error(response.text)
else:
if raw:
return response.text
else:
try:
data = response.json()
except ValueError:
raise CommentClientError(
u"Comments service returned invalid JSON for request {request_id}; first 100 characters: '{content}'".format(
request_id=request_id,
content=response.text[:100]
)
)
if paged_results:
dog_stats_api.histogram(
'comment_client.request.paged.result_count',
value=len(data.get('collection', [])),
tags=metric_tags
)
dog_stats_api.histogram(
'comment_client.request.paged.page',
value=data.get('page', 1),
tags=metric_tags
)
dog_stats_api.histogram(
'comment_client.request.paged.num_pages',
value=data.get('num_pages', 1),
tags=metric_tags
)
return data
class CommentClientError(Exception):
def __init__(self, msg):
self.message = msg
def __str__(self):
return repr(self.message)
class CommentClientRequestError(CommentClientError):
def __init__(self, msg, status_code=400):
super(CommentClientRequestError, self).__init__(msg)
self.status_code = status_code
class CommentClient500Error(CommentClientError):
pass
class CommentClientMaintenanceError(CommentClientError):
pass