diff --git a/package.json b/package.json index f3cb140c..1759dbf7 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@angular/platform-browser": "^8.2.11", "@angular/platform-browser-dynamic": "^8.2.11", "@angular/router": "^8.2.11", + "@sentry/browser": "^5.18.0", "angular-draggable-droppable": "^4.3.8", "angular-persistence": "^1.0.1", "angular-resizable-element": "^3.2.6", diff --git a/src/app/common/error-handlers/sentry-error-handler.ts b/src/app/common/error-handlers/sentry-error-handler.ts new file mode 100644 index 00000000..48acaa66 --- /dev/null +++ b/src/app/common/error-handlers/sentry-error-handler.ts @@ -0,0 +1,71 @@ +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule, ErrorHandler, Injectable, Injector, Inject } from "@angular/core"; +import { HttpErrorResponse } from "@angular/common/http"; + +import { environment } from "../../../environments/environment"; +import { AppComponent } from "../../app.component"; + +import * as Sentry from "@sentry/browser"; +import { BrowserOptions, init } from '@sentry/browser'; + +const config = { + dsn: "https://b2b1cfd9b043491eb6b566fd8acee358@o19455.ingest.sentry.io/842726" +}; +init(config as BrowserOptions); + +@Injectable() +export class SentryErrorHandler implements ErrorHandler { + constructor(@Inject(Injector) protected injector: Injector) {} + + extractError(error) { + // Try to unwrap zone.js error. + // https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts + if (error && error.ngOriginalError) { + error = error.ngOriginalError; + } + + // We can handle messages and Error objects directly. + if (typeof error === "string" || error instanceof Error) { + return error; + } + + // If it's http module error, extract as much information from it as we can. + if (error instanceof HttpErrorResponse) { + // The `error` property of http exception can be either an `Error` object, which we can use directly... + if (error.error instanceof Error) { + return error.error; + } + + // ... or an`ErrorEvent`, which can provide us with the message but no stack... + if (error.error instanceof ErrorEvent) { + return error.error.message; + } + + // ...or the request body itself, which we can use as a message instead. + if (typeof error.error === "string") { + return `Server returned code ${error.status} with body "${error.error}"`; + } + + // If we don't have any detailed information, fallback to the request message itself. + return error.message; + } + + // Skip if there's no error, and let user decide what to do with it. + return null; + } + + handleError(error) { + let extractedError = this.extractError(error) || "Handled unknown error"; + + // Capture handled exception and send it to Sentry. + const eventId = Sentry.captureException(extractedError); + + // When in development mode, log the error to console for immediate feedback. + if (!environment.production) { + console.error(extractedError); + } + + // Optionally show user dialog to provide details on what happened. + Sentry.showReportDialog({ eventId }); + } +} diff --git a/src/app/common/error-handlers/toaster-error-handler.ts b/src/app/common/error-handlers/toaster-error-handler.ts index 555cb22c..82b73a7d 100644 --- a/src/app/common/error-handlers/toaster-error-handler.ts +++ b/src/app/common/error-handlers/toaster-error-handler.ts @@ -1,7 +1,8 @@ import { RavenErrorHandler } from './raven-error-handler'; import { ToasterService } from '../../services/toaster.service'; +import { SentryErrorHandler } from './sentry-error-handler'; -export class ToasterErrorHandler extends RavenErrorHandler { +export class ToasterErrorHandler extends SentryErrorHandler { handleError(err: any): void { super.handleError(err); if (!err) return;