Skip to content

Commit

Permalink
Improve error reporting for unhandled rejections (#5575)
Browse files Browse the repository at this point in the history
* mark unhandled rejections as such for airbrake to get better error reporting

* use UnhandledRejection as error category and ensure that the backtrace of unhandled rejection survives

* update changelog

* clean up property for sending local errors
  • Loading branch information
philippotto authored Jun 22, 2021
1 parent b7a270f commit 1d26b79
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 30 additions & 17 deletions frontend/javascripts/libs/error_handling.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -24,7 +31,6 @@ const BLACKLISTED_ERROR_MESSAGES = [

type ErrorHandlingOptions = {
throwAssertions: boolean,
sendLocalErrors: boolean,
};

class ErrorWithParams extends Error {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
});
});

Expand Down Expand Up @@ -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;
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()]);
Expand Down

0 comments on commit 1d26b79

Please sign in to comment.