This captures real-time metrics for all of the comment client actions, segregated by course_id, as well as other small-cardinality fields. The goal is to be able to detect changes in forum usage, with the goal of alerting on potential error conditions.
148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
from contextlib import contextmanager
|
|
from dogapi import dog_stats_api
|
|
import json
|
|
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:
|
|
data = response.json()
|
|
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
|