You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2019/06/20 21:21:46 UTC

[airavata-django-portal] branch master updated: AIRAVATA-2638 Send frontend errors to server for logging

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git


The following commit(s) were added to refs/heads/master by this push:
     new b14450e  AIRAVATA-2638 Send frontend errors to server for logging
b14450e is described below

commit b14450ea0bd37965f6c9e40e663cc7dc760c1a74
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jun 20 17:21:31 2019 -0400

    AIRAVATA-2638 Send frontend errors to server for logging
---
 django_airavata/apps/api/package.json              |  1 +
 django_airavata/apps/api/serializers.py            |  7 +++++
 .../django_airavata_api/js/errors/ErrorReporter.js | 32 +++++++++++++++++++---
 .../api/static/django_airavata_api/js/index.js     |  1 +
 .../django_airavata_api/js/models/LogRecord.js     | 18 ++++++++++++
 .../django_airavata_api/js/service_config.js       | 15 ++++++++++
 django_airavata/apps/api/urls.py                   |  3 +-
 django_airavata/apps/api/views.py                  | 20 ++++++++++++++
 8 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/django_airavata/apps/api/package.json b/django_airavata/apps/api/package.json
index 01514e6..bb8155c 100644
--- a/django_airavata/apps/api/package.json
+++ b/django_airavata/apps/api/package.json
@@ -18,6 +18,7 @@
   "dependencies": {
     "@babel/polyfill": "^7.0.0",
     "@babel/runtime": "^7.1.2",
+    "stacktrace-js": "^2.0.0",
     "url-parse": "^1.4.3",
     "uuid": "^3.3.2"
   },
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 94a9f2b..740e1fd 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -878,3 +878,10 @@ class UnverifiedEmailUserProfile(serializers.Serializer):
         view_name='django_airavata_api:unverified-email-user-profile-detail',
         lookup_field='userId',
         lookup_url_kwarg='user_id')
+
+
+class LogRecordSerializer(serializers.Serializer):
+    level = serializers.CharField()
+    message = serializers.CharField()
+    details = StoredJSONField()
+    stacktrace = serializers.ListField(child=serializers.CharField())
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js
index 1b83d9d..8885588 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/errors/ErrorReporter.js
@@ -1,9 +1,33 @@
+import { services } from "..";
+import LogRecord from "../models/LogRecord";
+
+import StackTrace from "stacktrace-js";
 
 class ErrorReporter {
-    reportUnhandledError(unhandledError) {
-        // TODO: send to the server so it can be logged there
-        console.log(JSON.stringify(unhandledError, null, 4)); // eslint-disable-line no-console
-    }
+  reportUnhandledError(unhandledError) {
+    console.log(JSON.stringify(unhandledError, null, 4)); // eslint-disable-line no-console
+
+    StackTrace.fromError(unhandledError.error)
+      .then(stackframes => {
+        const stacktrace = stackframes.map(sf => sf.toString());
+        services.LoggingService.send(
+          {
+            data: new LogRecord({
+              level: "ERROR",
+              message: unhandledError.message,
+              details: unhandledError.details,
+              stacktrace: stacktrace
+            })
+          },
+          { ignoreErrors: true }
+        ).catch(err => {
+          console.log("Failed to log error", err); // eslint-disable-line no-console
+        });
+      })
+      .catch(err => {
+        console.log("Failed to product stacktrace", err); // eslint-disable-line no-console
+      });
+  }
 }
 
 export default new ErrorReporter();
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index c704fb4..b198da2 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -113,6 +113,7 @@ const services = {
   GroupResourceProfileService: ServiceFactory.service("GroupResourceProfiles"),
   GroupService: ServiceFactory.service("Groups"),
   LocaJobSubmissionService,
+  LoggingService: ServiceFactory.service("LogRecords"),
   IAMUserProfileService: ServiceFactory.service("IAMUserProfiles"),
   ParserService: ServiceFactory.service("Parsers"),
   ProjectService: ServiceFactory.service("Projects"),
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/LogRecord.js b/django_airavata/apps/api/static/django_airavata_api/js/models/LogRecord.js
new file mode 100644
index 0000000..78b0816
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/LogRecord.js
@@ -0,0 +1,18 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "level",
+  "message",
+  "details",
+  {
+    name: "stacktrace",
+    type: "string",
+    list: true
+  }
+];
+
+export default class LogRecord extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 473305e..792b920 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -14,6 +14,7 @@ import GatewayResourceProfile from "./models/GatewayResourceProfile";
 import Group from "./models/Group";
 import GroupResourceProfile from "./models/GroupResourceProfile";
 import IAMUserProfile from "./models/IAMUserProfile";
+import LogRecord from "./models/LogRecord";
 import Parser from "./models/Parser";
 import Project from "./models/Project";
 import SharedEntity from "./models/SharedEntity";
@@ -254,6 +255,20 @@ export default {
     queryParams: ["limit", "offset", "search"],
     modelClass: IAMUserProfile
   },
+  LogRecords: {
+    url: "/api/log",
+    methods: {
+      send: {
+        url: '/api/log',
+        requestType: "post",
+        bodyParams: {
+          name: "data"
+        },
+        modelClass: LogRecord
+      }
+    },
+    modelClass: LogRecord
+  },
   Parsers: {
     url: "/api/parsers",
     viewSet: true,
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 18d66ea..7f8cd2a 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -83,7 +83,8 @@ urlpatterns = [
         name="user-storage-items"),
     url(r'^experiment-statistics',
         views.ExperimentStatisticsView.as_view(),
-        name="experiment-statistics")
+        name="experiment-statistics"),
+    url(r'^log', views.LogRecordConsumer.as_view(), name='log')
 ]
 
 if logger.isEnabledFor(logging.DEBUG):
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 4b89615..73b5f02 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1,3 +1,4 @@
+import json
 import logging
 import os
 from datetime import datetime, timedelta
@@ -1541,3 +1542,22 @@ class UnverifiedEmailUserViewSet(mixins.ListModelMixin,
                 'creationTime': user_profile.creationTime,
             })
         return results
+
+
+class LogRecordConsumer(APIView):
+    serializer_class = serializers.LogRecordSerializer
+
+    def post(self, request, format=None):
+        serializer = self.serializer_class(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        log_record = serializer.validated_data
+        log_level = getattr(logging, log_record['level'], None)
+        if log_level is not None:
+            stacktrace = "".join(
+                map(lambda a: "\n    " + a, log_record['stacktrace']))
+            log.log(log_level,
+                    "Frontend error: {}: {}\nstacktrace: {}".format(
+                        log_record['message'],
+                        json.dumps(log_record['details'], indent=4),
+                        stacktrace))
+        return Response(serializer.data)