-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
errorhandler.ts
135 lines (113 loc) · 4.59 KB
/
errorhandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler as AngularErrorHandler, Inject, Injectable } from '@angular/core';
import * as Sentry from '@sentry/browser';
import { captureException } from '@sentry/browser';
import { addExceptionMechanism } from '@sentry/utils';
import { runOutsideAngular } from './zone';
/**
* Options used to configure the behavior of the Angular ErrorHandler.
*/
export interface ErrorHandlerOptions {
logErrors?: boolean;
showDialog?: boolean;
dialogOptions?: Sentry.ReportDialogOptions;
/**
* Custom implementation of error extraction from the raw value captured by the Angular.
* @param error Value captured by Angular's ErrorHandler provider
* @param defaultExtractor Default implementation that can be used as the fallback in case of custom implementation
*/
extractor?(error: unknown, defaultExtractor: (error: unknown) => unknown): unknown;
}
/**
* Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one.
*/
@Injectable({ providedIn: 'root' })
class SentryErrorHandler implements AngularErrorHandler {
protected readonly _options: ErrorHandlerOptions;
public constructor(@Inject('errorHandlerOptions') options?: ErrorHandlerOptions) {
this._options = {
logErrors: true,
...options,
};
}
/**
* Method called for every value captured through the ErrorHandler
*/
public handleError(error: unknown): void {
const extractedError = this._extractError(error) || 'Handled unknown error';
// Capture handled exception and send it to Sentry.
const eventId = runOutsideAngular(() =>
captureException(extractedError, scope => {
scope.addEventProcessor(event => {
addExceptionMechanism(event, {
type: 'angular',
handled: false,
});
return event;
});
return scope;
}),
);
// When in development mode, log the error to console for immediate feedback.
if (this._options.logErrors) {
// eslint-disable-next-line no-console
console.error(extractedError);
}
// Optionally show user dialog to provide details on what happened.
if (this._options.showDialog) {
Sentry.showReportDialog({ ...this._options.dialogOptions, eventId });
}
}
/**
* Used to pull a desired value that will be used to capture an event out of the raw value captured by ErrorHandler.
*/
protected _extractError(error: unknown): unknown {
// Allow custom overrides of extracting function
if (this._options.extractor) {
const defaultExtractor = this._defaultExtractor.bind(this);
return this._options.extractor(error, defaultExtractor);
}
return this._defaultExtractor(error);
}
/**
* Default implementation of error extraction that handles default error wrapping, HTTP responses, ErrorEvent and few other known cases.
*/
protected _defaultExtractor(errorCandidate: unknown): unknown {
let error = errorCandidate;
// Try to unwrap zone.js error.
// https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts
if (error && (error as { ngOriginalError: Error }).ngOriginalError) {
error = (error as { ngOriginalError: 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 && error.error.message) {
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;
}
// Nothing was extracted, fallback to default error message.
return null;
}
}
/**
* Factory function that creates an instance of a preconfigured ErrorHandler provider.
*/
function createErrorHandler(config?: ErrorHandlerOptions): SentryErrorHandler {
return new SentryErrorHandler(config);
}
export { createErrorHandler, SentryErrorHandler };