diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index fad47955491..0bdd1aaa145 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Added compatibility with newer JREs, tested with 8, 11 and 14. [#5558](https://github.com/scalableminds/webknossos/pull/5558) ### Changed +- Improve error logging for unhandled rejections. [#5575](https://github.com/scalableminds/webknossos/pull/5575) - Improved dragging behavior of trees/groups in the tree tab. [#5573](https://github.com/scalableminds/webknossos/pull/5573) - "Center new Nodes" option was renamed to "Auto-center Nodes" and changed to also influence the centering-behavior when deleting a node. [#5538](https://github.com/scalableminds/webknossos/pull/5538) - The displayed webKnossos version now omits the parent release name for intermediate builds. [#5565](https://github.com/scalableminds/webknossos/pull/5565) diff --git a/conf/application.conf b/conf/application.conf index 48240b60e77..feb05255baa 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -228,8 +228,8 @@ braintracing { # Front-end analytics airbrake { environment = "dev-local" - projectKey = "empty" - projectID = "empty" + projectKey = "insert-valid-projectKey-here" + projectID = "insert-valid-projectID-here" } # Front-end analytics diff --git a/frontend/javascripts/libs/error_handling.js b/frontend/javascripts/libs/error_handling.js index 68ee63d4372..0e79a034926 100644 --- a/frontend/javascripts/libs/error_handling.js +++ b/frontend/javascripts/libs/error_handling.js @@ -11,6 +11,13 @@ import Toast from "libs/toast"; import messages from "messages"; import window, { document, location } from "libs/window"; +// Note that if you set this value to true for debugging airbrake reporting, +// you also need to set the values for projectID and projectKey in application.conf +const LOG_LOCAL_ERRORS = false; + +const UNHANDLED_REJECTION_LABEL = "UnhandledRejection"; +const UNHANDLED_REJECTION_PREFIX = `${UNHANDLED_REJECTION_LABEL}: `; + // No more than MAX_NUM_ERRORS will be reported to airbrake const MAX_NUM_ERRORS = 50; const BLACKLISTED_ERROR_MESSAGES = [ @@ -24,7 +31,6 @@ const BLACKLISTED_ERROR_MESSAGES = [ type ErrorHandlingOptions = { throwAssertions: boolean, - sendLocalErrors: boolean, }; class ErrorWithParams extends Error { @@ -54,17 +60,15 @@ export function handleGenericError(error: { ...Error, messages?: mixed }) { class ErrorHandling { throwAssertions: boolean; - sendLocalErrors: boolean; commitHash: ?string; airbrake: typeof AirbrakeClient; numberOfErrors: number = 0; initialize(options: ErrorHandlingOptions) { if (options == null) { - options = { throwAssertions: false, sendLocalErrors: false }; + options = { throwAssertions: false }; } this.throwAssertions = options.throwAssertions; - this.sendLocalErrors = options.sendLocalErrors; const metaElement = document.querySelector("meta[name='commit-hash']"); this.commitHash = metaElement ? metaElement.getAttribute("content") : null; @@ -105,24 +109,26 @@ class ErrorHandling { return null; }); - if (!this.sendLocalErrors) { - this.airbrake.addFilter(notice => { - if (location.hostname !== "127.0.0.1" && location.hostname !== "localhost") { - return notice; - } - return null; - }); - } + this.airbrake.addFilter(notice => { + if ( + LOG_LOCAL_ERRORS || + (location.hostname !== "127.0.0.1" && location.hostname !== "localhost") + ) { + return notice; + } + return null; + }); // Remove airbrake's unhandledrejection handler window.removeEventListener("unhandledrejection", this.airbrake.onUnhandledrejection); window.addEventListener("unhandledrejection", event => { // Create our own error for unhandled rejections here to get additional information for [Object object] errors in airbrake - const originalError = event.reason instanceof Error ? event.reason.toString() : event.reason; - // Put the actual error into the main string so that not all unhandled errors are grouped - // together in airbrake - this.notify(Error(`Unhandled Rejection: ${JSON.stringify(originalError).slice(0, 80)}`), { - originalError, + const reasonAsString = event.reason instanceof Error ? event.reason.toString() : event.reason; + const wrappedError = event.reason instanceof Error ? event.reason : new Error(event.reason); + wrappedError.message = + UNHANDLED_REJECTION_PREFIX + JSON.stringify(reasonAsString).slice(0, 80); + this.notify(wrappedError, { + originalError: reasonAsString, }); }); @@ -161,6 +167,13 @@ class ErrorHandling { assertExtendContext(additionalContext: Object) { this.airbrake.addFilter(notice => { + notice.errors.forEach(error => { + const index = error.message.indexOf(UNHANDLED_REJECTION_PREFIX); + if (index > -1) { + error.type = UNHANDLED_REJECTION_LABEL; + } + }); + Object.assign(notice.context, additionalContext); return notice; }); diff --git a/frontend/javascripts/main.js b/frontend/javascripts/main.js index da968cd7814..661b255c54a 100644 --- a/frontend/javascripts/main.js +++ b/frontend/javascripts/main.js @@ -41,7 +41,7 @@ async function loadHasOrganizations() { } document.addEventListener("DOMContentLoaded", async () => { - ErrorHandling.initialize({ throwAssertions: false, sendLocalErrors: false }); + ErrorHandling.initialize({ throwAssertions: false }); document.addEventListener("click", googleAnalyticsLogClicks); await Promise.all([loadFeatureToggles(), loadActiveUser(), loadHasOrganizations()]);