From e04ccc33889e4f5c87526eb5cb61cffd08c75b16 Mon Sep 17 00:00:00 2001 From: Benjamin Ledel <benjamin@schule-plus.com> Date: Wed, 5 Mar 2025 16:53:48 +0100 Subject: [PATCH] * add prometheus counter --- src/backend/settings.py | 4 +- src/backend/urls.py | 1 + src/xapi/views.py | 89 +++++++++++++++++++++++++++++++---------- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/backend/settings.py b/src/backend/settings.py index d62c12f..596e837 100644 --- a/src/backend/settings.py +++ b/src/backend/settings.py @@ -266,4 +266,6 @@ SHIB_ID_CONNECTOR_URL = env("SHIB_ID_CONNECTOR_URL",default="") SHIB_ID_CONNECTOR_APP_SECRET = env("SHIB_ID_CONNECTOR_APP_SECRET",default="") SHIB_ID_CONNECTOR_PROCESS_ID = env("SHIB_ID_CONNECTOR_PROCESS_ID",default="PI") SHIB_ID_CONNECTOR_LINK_TYPE = env("SHIB_ID_CONNECTOR_LINK_TYPE",default="") -SHIB_ID_CONNECTOR_USE_FILE_MAPPING = env("SHIB_ID_CONNECTOR_USE_FILE_MAPPING",default=False) \ No newline at end of file +SHIB_ID_CONNECTOR_USE_FILE_MAPPING = env("SHIB_ID_CONNECTOR_USE_FILE_MAPPING",default=False) + +PROMETHEUS_METRIC_NAMESPACE = "polaris" \ No newline at end of file diff --git a/src/backend/urls.py b/src/backend/urls.py index e883672..9a7e20b 100644 --- a/src/backend/urls.py +++ b/src/backend/urls.py @@ -48,6 +48,7 @@ def get_static_text(filename): urlpatterns = [ path('robots.txt', get_static_text('static/robots_deny.txt' if settings.ALLOW_ROBOTS == "False" or settings.ALLOW_ROBOTS == False else "static/robots_allow.txt")), path("admin/", admin.site.urls), + path('', include('django_prometheus.urls')), path("api/v1/consents/", include("consents.urls")), path("api/v1/auth/", include("users.urls")), path("api/v1/provider/", include("providers.urls")), diff --git a/src/xapi/views.py b/src/xapi/views.py index 0e4013e..9bfe2b9 100644 --- a/src/xapi/views.py +++ b/src/xapi/views.py @@ -15,6 +15,7 @@ from jsonschema import ValidationError, validate from jsonschema.validators import validator_for from rest_framework import status from rest_framework.views import APIView +from prometheus_client import Counter from backend.utils import lrs_db from consents.models import UserConsents @@ -28,6 +29,17 @@ from .tasks import retry_forward_statements PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) +# Prometheus Counters +STATEMENTS_PROCESSED = Counter( + "xapi_statements_processed_total", "Total number of xAPI statements processed" +) +STATEMENTS_ACCEPTED = Counter( + "xapi_statements_accepted_total", "Total number of xAPI statements accepted" +) +STATEMENTS_REJECTED = Counter( + "xapi_statements_rejected_total", "Total number of xAPI statements rejected" +) + with open(os.path.join(PROJECT_PATH, "static/xapi_statement.schema.json")) as f: schema = json.loads(f.read()) cls = validator_for(schema) @@ -285,13 +297,14 @@ def process_tan_statement(x_api_statement): class CreateStatement(APIView): """ - xAPI create statements proxy. This endpoint filter xAPI statements based and user settings and passes only consented xAPI statements to the LRS. + xAPI create statements proxy. This endpoint filters xAPI statements based on user settings + and passes only consented xAPI statements to the LRS. """ def post(self, request): - # provider authorization + # Provider authorization auth_header = request.headers.get("Authorization") - if not auth_header.startswith("Basic "): + if not auth_header or not auth_header.startswith("Basic "): return JsonResponse( { "message": "No provider authorization token supplied.", @@ -300,6 +313,7 @@ class CreateStatement(APIView): safe=False, status=status.HTTP_401_UNAUTHORIZED, ) + auth_key = auth_header.split(" ")[1] try: provider_auth = ProviderAuthorization.objects.get(key=auth_key) @@ -315,7 +329,7 @@ class CreateStatement(APIView): provider = provider_auth.provider - # load latest provider schema for essential verbs + # Load latest provider schema for essential verbs try: latest_schema = ProviderSchema.objects.get( provider=provider, superseded_by__isnull=True @@ -330,29 +344,52 @@ class CreateStatement(APIView): status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) - # handle list of xAPI statements as well as single xAPI statement + # Handle list of xAPI statements as well as a single statement x_api_statements = ( request.data if isinstance(request.data, list) else [request.data] ) - # forward to other LRS without validation etc., if given - if latest_schema.additional_lrs and isinstance(latest_schema.additional_lrs, list) and len(latest_schema.additional_lrs) > 0: + # Track total processed statements + STATEMENTS_PROCESSED.inc(len(x_api_statements)) + + # Forward to additional LRS if configured + if ( + latest_schema.additional_lrs + and isinstance(latest_schema.additional_lrs, list) + and len(latest_schema.additional_lrs) > 0 + ): for additional_lrs in latest_schema.additional_lrs: - headers = {"Authorization": additional_lrs["token_type"] + " " + additional_lrs["token"]} + headers = { + "Authorization": additional_lrs["token_type"] + + " " + + additional_lrs["token"] + } try: - res = requests.post(additional_lrs["url"], json=x_api_statements, headers=headers) + res = requests.post( + additional_lrs["url"], json=x_api_statements, headers=headers + ) if res.status_code != 200: raise RuntimeError("Returned status code other than 200") if settings.DEBUG: - print("Forwarded statement to ", additional_lrs["url"], ":", res.reason, - "({})".format(res.status_code)) + print( + "Forwarded statement to ", + additional_lrs["url"], + ":", + res.reason, + "({})".format(res.status_code), + ) except Exception as e: if settings.DEBUG: print("Could not forward to ", additional_lrs["url"], ":", e) - retry_forward_statements.delay(x_api_statements, additional_lrs["token_type"], additional_lrs["token"], additional_lrs["url"]) + retry_forward_statements.delay( + x_api_statements, + additional_lrs["token_type"], + additional_lrs["token"], + additional_lrs["url"], + ) if settings.SHOW_XAPI_STATEMENTS: - print(x_api_statements) + print(x_api_statements) try: result = [ @@ -367,15 +404,19 @@ class CreateStatement(APIView): status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) - invalid_or_not_consented = ( - len([e for e in result if e["valid"] == False or e["accepted"] == False]) - > 0 - ) - + # Count rejected and accepted statements + rejected_count = sum( + 1 for e in result if e["valid"] is False or e["accepted"] is False + ) + accepted_count = len(x_api_statements) - rejected_count + + STATEMENTS_REJECTED.inc(rejected_count) + STATEMENTS_ACCEPTED.inc(accepted_count) + if settings.SHOW_XAPI_STATEMENTS: print(result) - if invalid_or_not_consented: + if rejected_count > 0: return JsonResponse( { "message": "xAPI statements couldn't be stored in LRS", @@ -384,9 +425,13 @@ class CreateStatement(APIView): status=status.HTTP_400_BAD_REQUEST, ) else: - # anonymize statements - x_api_statements = [anonymize_statement(statement) if result[i].get("needs_anonymization", False) - else statement for i, statement in enumerate(x_api_statements)] + # Anonymize statements where needed + x_api_statements = [ + anonymize_statement(statement) + if result[i].get("needs_anonymization", False) + else statement + for i, statement in enumerate(x_api_statements) + ] uuids = list(map(store_in_db, x_api_statements)) return JsonResponse( { -- GitLab