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)