From bd1e6d97ed37c7c983fe106a29be08933c871317 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Wed, 24 Jul 2019 16:51:36 +0500 Subject: [PATCH] improve monitoring for DSC flow ENT-1953 --- CHANGELOG.rst | 5 + consent/api/v1/views.py | 51 +++++- enterprise/__init__.py | 2 +- enterprise/views.py | 263 +++++++++++++++++++++---------- enterprise/views_error_codes.txt | 3 +- 5 files changed, 234 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index edc26b2d51..79fc55298a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,11 @@ Change Log Unreleased ---------- +[1.8.6] - 2019-07-25 +-------------------- + +* Add/Update logs for GrantDataSharingPermissions and DataSharingConsentView views to improve monitoring. + [1.8.5] - 2019-07-25 -------------------- diff --git a/consent/api/v1/views.py b/consent/api/v1/views.py index 35b9c551b2..489ab29780 100644 --- a/consent/api/v1/views.py +++ b/consent/api/v1/views.py @@ -5,6 +5,8 @@ from __future__ import absolute_import, unicode_literals +from logging import getLogger + from edx_rest_framework_extensions.auth.bearer.authentication import BearerAuthentication from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from rest_framework.authentication import SessionAuthentication @@ -18,6 +20,8 @@ from enterprise.api.throttles import ServiceUserThrottle from enterprise.utils import get_request_value +LOGGER = getLogger(__name__) + class DataSharingConsentView(APIView): """ @@ -85,12 +89,29 @@ def get_consent_record(self, request): Get the consent record relevant to the request at hand. """ username, course_id, program_uuid, enterprise_customer_uuid = self.get_required_query_params(request) - return get_data_sharing_consent( + dsc = get_data_sharing_consent( username, enterprise_customer_uuid, course_id=course_id, program_uuid=program_uuid ) + if not dsc: + log_message = ( + '[Enterprise Consent API] The code was unable to get consent data for the user. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {username}, ' + 'ErrorCode: {error_code}'.format( + course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer_uuid, + username=username, + error_code='ENTGDS001', + ) + ) + LOGGER.error(log_message) + return dsc def get_required_query_params(self, request): """ @@ -105,13 +126,29 @@ def get_required_query_params(self, request): program_uuid = get_request_value(request, self.REQUIRED_PARAM_PROGRAM_UUID, '') enterprise_customer_uuid = get_request_value(request, self.REQUIRED_PARAM_ENTERPRISE_CUSTOMER) if not (username and (course_id or program_uuid) and enterprise_customer_uuid): - raise ConsentAPIRequestError( - self.get_missing_params_message([ - ("'username'", bool(username)), - ("'enterprise_customer_uuid'", bool(enterprise_customer_uuid)), - ("one of 'course_id' or 'program_uuid'", bool(course_id or program_uuid)), - ]) + exception_message = self.get_missing_params_message([ + ("'username'", bool(username)), + ("'enterprise_customer_uuid'", bool(enterprise_customer_uuid)), + ("one of 'course_id' or 'program_uuid'", bool(course_id or program_uuid)), + ]) + log_message = ( + '[Enterprise Consent API] Required request values missing for action to be carried out. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {username}, ' + 'ErrorCode: {error_code}, ' + 'Message: {message}'.format( + course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer_uuid, + username=username, + error_code='ENTGDS000', + message=exception_message, + ) ) + LOGGER.error(log_message) + raise ConsentAPIRequestError(exception_message) return username, course_id, program_uuid, enterprise_customer_uuid def get_missing_params_message(self, parameter_state): diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 2fb5b92385..8df4715b5b 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -4,6 +4,6 @@ from __future__ import absolute_import, unicode_literals -__version__ = "1.8.5" +__version__ = "1.8.6" default_app_config = "enterprise.apps.EnterpriseConfig" # pylint: disable=invalid-name diff --git a/enterprise/views.py b/enterprise/views.py index fcb018e79d..ba31f396ea 100644 --- a/enterprise/views.py +++ b/enterprise/views.py @@ -189,7 +189,21 @@ def course_or_program_exist(self, course_id, program_uuid): course_exists = course_id and CourseApiClient().get_course_details(course_id) program_exists = program_uuid and get_course_catalog_api_service_client().program_exists(program_uuid) return course_exists or program_exists - except ImproperlyConfigured: + except ImproperlyConfigured as error: + error_code = 'ENTGDS004' + log_message = ( + '[Enterprise DSC API] Course catalog api configuration error. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'ErrorCode: {error_code}, ' + 'Message: {message}'.format( + course_id=course_id, + program_uuid=program_uuid, + error_code=error_code, + message=error, + ) + ) + LOGGER.error(log_message) return False def get_default_context(self, enterprise_customer, platform_name): @@ -360,7 +374,23 @@ def get_course_or_program_context(self, enterprise_customer, course_id=None, pro if not self.preview_mode: try: catalog_api_client = get_course_catalog_api_service_client(enterprise_customer.site) - except ImproperlyConfigured: + except ImproperlyConfigured as error: + error_code = 'ENTGDS004' + log_message = ( + '[Enterprise DSC API] Course catalog api configuration error. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'ErrorCode: {error_code}, ' + 'Message: {message}'.format( + course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer.uuid, + error_code=error_code, + message=error, + ) + ) + LOGGER.error(log_message) raise Http404 course_run_details = catalog_api_client.get_course_run(course_id) @@ -439,7 +469,7 @@ def get_page_language_context_data( return context_data @method_decorator(login_required) - def get(self, request): + def get(self, request): # pylint: disable=too-many-statements """ Render a form to collect user input about data sharing consent. """ @@ -452,22 +482,44 @@ def get(self, request): # Get enterprise_customer to start in case we need to render a custom 404 page # Then go through other business logic to determine (and potentially overwrite) the enterprise customer - enterprise_customer = get_enterprise_customer_or_404(enterprise_customer_uuid) + try: + enterprise_customer = get_enterprise_customer_or_404(enterprise_customer_uuid) + except Http404: + error_code = 'ENTGDS008' + log_message = ( + '[Enterprise DSC API] No record found for Enterprise customer. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}'.format( + course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer_uuid, + user_id=request.user.id, + error_code=error_code, + ) + ) + LOGGER.error(log_message) + raise + context_data = get_global_context(request, enterprise_customer) if not self.preview_mode: if not self.course_or_program_exist(course_id, program_uuid): error_code = 'ENTGDS000' log_message = ( - 'Neither the course with course_id: {course_id} ' - 'or program with {program_uuid} exist for ' - 'enterprise customer {enterprise_customer_uuid}' - 'Error code {error_code} presented to user {userid}'.format( + '[Enterprise DSC API] The course or program with a given id does not exist. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code} '.format( course_id=course_id, program_uuid=program_uuid, - error_code=error_code, - userid=request.user.id, enterprise_customer_uuid=enterprise_customer_uuid, + user_id=request.user.id, + error_code=error_code, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -479,19 +531,22 @@ def get(self, request): program_uuid=program_uuid, course_id=course_id ) - except NotConnectedToOpenEdX as error: - error_code = 'ENTGDS001' + except ImproperlyConfigured as error: + error_code = 'ENTGDS004' log_message = ( - 'The was a problem with getting the consent record of user {userid} with ' - 'uuid {enterprise_customer_uuid}. get_data_sharing_consent threw ' - 'the following NotConnectedToOpenEdX error: {error}' - 'for course_id {course_id}.' - 'Error code {error_code} presented to user'.format( - userid=request.user.id, + '[Enterprise DSC API] Course catalog api configuration error. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}, ' + 'Message: {message}'.format( + course_id=course_id, + program_uuid=program_uuid, enterprise_customer_uuid=enterprise_customer_uuid, - error=error, + user_id=request.user.id, error_code=error_code, - course_id=course_id, + message=error, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -500,17 +555,22 @@ def get(self, request): consent_required = consent_record.consent_required() except AttributeError: consent_required = None - except ImproperlyConfigured: + except ImproperlyConfigured as error: error_code = 'ENTGDS004' log_message = ( - 'CourseCatalogApiServiceClient is improperly configured. ' - 'Returned error code {error_code} to user {userid} ' - 'and enterprise_customer {enterprise_customer} ' - 'for course_id {course_id}'.format( - error_code=error_code, - userid=request.user.id, - enterprise_customer=enterprise_customer.uuid, + '[Enterprise DSC API] Course catalog api configuration error. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}, ' + 'Message: {message}'.format( course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer_uuid, + user_id=request.user.id, + error_code=error_code, + message=error, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -518,17 +578,22 @@ def get(self, request): if consent_record is None or not consent_required: error_code = 'ENTGDS002' log_message = ( - 'The was a problem with the consent record of user {userid} with ' - 'enterprise_customer_uuid {enterprise_customer_uuid}. consent_record has a value ' - 'of {consent_record} and consent_record.consent_required() a ' - 'value of {consent_required} for course_id {course_id}. ' - 'Error code {error_code} presented to user'.format( - userid=request.user.id, + '[Enterprise DSC API] There is no consent record, or consent is not required. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}, ' + 'Context: {context}'.format( + course_id=course_id, + program_uuid=program_uuid, enterprise_customer_uuid=enterprise_customer_uuid, - consent_record=consent_record, - consent_required=consent_required, + user_id=request.user.id, error_code=error_code, - course_id=course_id, + context={ + 'consent_record': consent_record, + 'consent_required': consent_required, + }, ) ) LOGGER.info(log_message) @@ -541,20 +606,25 @@ def get(self, request): # Retrieve context data again now that enterprise_customer logic has been run context_data = get_global_context(request, enterprise_customer) - if not (enterprise_customer_uuid and success_url and failure_url): + if not (success_url and failure_url): error_code = 'ENTGDS003' log_message = ( - 'Error: one or more of the following values was falsy: ' - 'enterprise_customer_uuid: {enterprise_customer_uuid}, ' - 'success_url: {success_url}, ' - 'failure_url: {failure_url} for course id {course_id}' - 'The following error code was reported to user {userid}: {error_code}'.format( - userid=request.user.id, + '[Enterprise DSC API] Required request values missing for action to be carried out. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}, ' + 'Context: {context}'.format( + course_id=course_id, + program_uuid=program_uuid, enterprise_customer_uuid=enterprise_customer_uuid, - success_url=success_url, - failure_url=failure_url, + user_id=request.user.id, error_code=error_code, - course_id=course_id, + context={ + 'success_url': success_url, + 'failure_url': failure_url, + }, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -569,14 +639,17 @@ def get(self, request): except Http404: error_code = 'ENTGDS004' log_message = ( - 'CourseCatalogApiServiceClient is improperly configured. ' - 'Returned error code {error_code} to user {userid} ' - 'and enterprise_customer {enterprise_customer} ' - 'for course_id {course_id}'.format( - error_code=error_code, - userid=request.user.id, - enterprise_customer=enterprise_customer.uuid, + '[Enterprise DSC API] Course catalog api configuration error. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}'.format( course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_customer_uuid, + user_id=request.user.id, + error_code=error_code, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -603,23 +676,48 @@ def post(self, request): course_id = request.POST.get('course_id', '') program_uuid = request.POST.get('program_uuid', '') - enterprise_customer = get_enterprise_customer_or_404(enterprise_uuid) + try: + enterprise_customer = get_enterprise_customer_or_404(enterprise_uuid) + except Http404: + error_code = 'ENTGDS008' + log_message = ( + '[Enterprise DSC API] No record found for Enterprise customer. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}'.format( + course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_uuid, + user_id=request.user.id, + error_code=error_code, + ) + ) + LOGGER.error(log_message) + raise + context_data = get_global_context(request, enterprise_customer) - if not (enterprise_uuid and success_url and failure_url): + if not (success_url and failure_url): error_code = 'ENTGDS005' log_message = ( - 'Error: one or more of the following values was falsy: ' - 'enterprise_uuid: {enterprise_uuid}, ' - 'success_url: {success_url}, ' - 'failure_url: {failure_url} for course_id {course_id}. ' - 'The following error code was reported to the user {userid}: {error_code}'.format( - userid=request.user.id, - enterprise_uuid=enterprise_uuid, - success_url=success_url, - failure_url=failure_url, - error_code=error_code, + '[Enterprise DSC API] Required request values missing for action to be carried out. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}, ' + 'Context: {context}'.format( course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_uuid, + user_id=request.user.id, + error_code=error_code, + context={ + 'success_url': success_url, + 'failure_url': failure_url, + }, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -627,15 +725,17 @@ def post(self, request): if not self.course_or_program_exist(course_id, program_uuid): error_code = 'ENTGDS006' log_message = ( - 'Neither the course with course_id: {course_id} ' - 'or program with {program_uuid} exist for ' - 'enterprise customer {enterprise_uuid}' - 'Error code {error_code} presented to user {userid}'.format( + '[Enterprise DSC API] The course or program with a given id does not exist. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}'.format( course_id=course_id, program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_uuid, + user_id=request.user.id, error_code=error_code, - userid=request.user.id, - enterprise_uuid=enterprise_uuid, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) @@ -649,16 +749,17 @@ def post(self, request): if consent_record is None: error_code = 'ENTGDS007' log_message = ( - 'The was a problem with the consent record of user {userid} with ' - 'enterprise_uuid {enterprise_uuid}. consent_record has a value ' - 'of {consent_record} and a ' - 'value for course_id {course_id}. ' - 'Error code {error_code} presented to user'.format( - userid=request.user.id, - enterprise_uuid=enterprise_uuid, - consent_record=consent_record, - error_code=error_code, + '[Enterprise DSC API] There is no consent record, or consent is not required. ' + 'Course: {course_id}, ' + 'Program: {program_uuid}, ' + 'EnterpriseCustomer: {enterprise_customer_uuid}, ' + 'User: {user_id}, ' + 'ErrorCode: {error_code}'.format( course_id=course_id, + program_uuid=program_uuid, + enterprise_customer_uuid=enterprise_uuid, + user_id=request.user.id, + error_code=error_code, ) ) return render_page_with_error_code_message(request, context_data, error_code, log_message) diff --git a/enterprise/views_error_codes.txt b/enterprise/views_error_codes.txt index 5ff6ebe815..5982832b69 100644 --- a/enterprise/views_error_codes.txt +++ b/enterprise/views_error_codes.txt @@ -10,6 +10,7 @@ ENTGDS004 - Course catalog api configuration error ENTGDS005 - Required request values missing for action to be carried out ENTGDS006 - The course or program with a given id does not exist ENTGDS007 - There is no consent record, or consent is not required +ENTGDS008 - No record found for Enterprise customer ENTHCE000 - No course modes for the goven course id @@ -20,4 +21,4 @@ ENTPEV003 - No program details available ENTPEV004 - No program type available ENTRV000 - No course_run_id available -ENTRV001 - No course_run_id available, or course catalog configuration error +ENTRV001 - No course_run_id available, or course catalog configuration error