Skip to content

Commit 3665831

Browse files
authored
ref(angular): Set ErrorHandler Exception Mechanism to be unhandled by default(#3844)
Previously, exceptions caught in the Sentry `ErrorHandler` defaulted to the generic mechanism, meaning they were marked as `handled: true` and `mechanism: generic`. This patch adds a custom mechanism for these events: `handled: false` (because they were caught in our ErrorHandler) and `mechanism: angular`.
1 parent 25c3dc7 commit 3665831

File tree

2 files changed

+140
-1
lines changed

2 files changed

+140
-1
lines changed

packages/angular/src/errorhandler.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { HttpErrorResponse } from '@angular/common/http';
22
import { ErrorHandler as AngularErrorHandler, Inject, Injectable } from '@angular/core';
33
import * as Sentry from '@sentry/browser';
4+
import { captureException } from '@sentry/browser';
5+
import { addExceptionMechanism } from '@sentry/utils';
46

57
import { runOutsideAngular } from './zone';
68

@@ -40,7 +42,20 @@ class SentryErrorHandler implements AngularErrorHandler {
4042
const extractedError = this._extractError(error) || 'Handled unknown error';
4143

4244
// Capture handled exception and send it to Sentry.
43-
const eventId = runOutsideAngular(() => Sentry.captureException(extractedError));
45+
const eventId = runOutsideAngular(() =>
46+
captureException(extractedError, scope => {
47+
scope.addEventProcessor(event => {
48+
addExceptionMechanism(event, {
49+
type: 'angular',
50+
handled: false,
51+
});
52+
53+
return event;
54+
});
55+
56+
return scope;
57+
}),
58+
);
4459

4560
// When in development mode, log the error to console for immediate feedback.
4661
if (this._options.logErrors) {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { HttpErrorResponse } from '@angular/common/http';
2+
import * as SentryBrowser from '@sentry/browser';
3+
import { Scope } from '@sentry/browser';
4+
import * as SentryUtils from '@sentry/utils';
5+
6+
import { createErrorHandler, SentryErrorHandler } from '../src/errorhandler';
7+
8+
const FakeScope = new Scope();
9+
10+
jest.mock('@sentry/browser', () => {
11+
const original = jest.requireActual('@sentry/browser');
12+
return {
13+
...original,
14+
captureException: (err: unknown, cb: (arg0?: unknown) => unknown) => {
15+
cb(FakeScope);
16+
return original.captureException(err, cb);
17+
},
18+
};
19+
});
20+
21+
const captureExceptionSpy = jest.spyOn(SentryBrowser, 'captureException');
22+
23+
jest.spyOn(console, 'error').mockImplementation();
24+
25+
describe('SentryErrorHandler', () => {
26+
beforeEach(() => {
27+
jest.clearAllMocks();
28+
});
29+
30+
it('`createErrorHandler `creates a SentryErrorHandler with an empty config', () => {
31+
const errorHandler = createErrorHandler();
32+
33+
expect(errorHandler).toBeInstanceOf(SentryErrorHandler);
34+
});
35+
36+
it('handleError method assigns the correct mechanism', () => {
37+
const addEventProcessorSpy = jest.spyOn(FakeScope, 'addEventProcessor').mockImplementationOnce(callback => {
38+
void callback({}, { event_id: 'fake-event-id' });
39+
return FakeScope;
40+
});
41+
42+
const addExceptionMechanismSpy = jest.spyOn(SentryUtils, 'addExceptionMechanism');
43+
44+
const errorHandler = createErrorHandler();
45+
errorHandler.handleError(new Error('test'));
46+
47+
expect(addEventProcessorSpy).toBeCalledTimes(1);
48+
expect(addExceptionMechanismSpy).toBeCalledTimes(1);
49+
expect(addExceptionMechanismSpy).toBeCalledWith({}, { handled: false, type: 'angular' });
50+
});
51+
52+
it('handleError method extracts `null` error', () => {
53+
createErrorHandler().handleError(null);
54+
55+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
56+
expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', expect.any(Function));
57+
});
58+
59+
it('handleError method extracts `undefined` error', () => {
60+
createErrorHandler().handleError(undefined);
61+
62+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
63+
expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', expect.any(Function));
64+
});
65+
66+
it('handleError method extracts a string', () => {
67+
const str = 'sentry-test';
68+
createErrorHandler().handleError(str);
69+
70+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
71+
expect(captureExceptionSpy).toHaveBeenCalledWith(str, expect.any(Function));
72+
});
73+
74+
it('handleError method extracts an empty Error', () => {
75+
const err = new Error();
76+
createErrorHandler().handleError(err);
77+
78+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
79+
expect(captureExceptionSpy).toHaveBeenCalledWith(err, expect.any(Function));
80+
});
81+
82+
it('handleError method extracts an Error with `ngOriginalError`', () => {
83+
const ngErr = new Error('sentry-ng-test');
84+
const err = {
85+
ngOriginalError: ngErr,
86+
};
87+
88+
createErrorHandler().handleError(err);
89+
90+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
91+
expect(captureExceptionSpy).toHaveBeenCalledWith(ngErr, expect.any(Function));
92+
});
93+
94+
it('handleError method extracts an `HttpErrorResponse` with `Error`', () => {
95+
const httpErr = new Error('sentry-http-test');
96+
const err = new HttpErrorResponse({ error: httpErr });
97+
98+
createErrorHandler().handleError(err);
99+
100+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
101+
expect(captureExceptionSpy).toHaveBeenCalledWith(httpErr, expect.any(Function));
102+
});
103+
104+
it('handleError method extracts an `HttpErrorResponse` with `ErrorEvent`', () => {
105+
const httpErr = new ErrorEvent('http', { message: 'sentry-http-test' });
106+
const err = new HttpErrorResponse({ error: httpErr });
107+
108+
createErrorHandler().handleError(err);
109+
110+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
111+
expect(captureExceptionSpy).toHaveBeenCalledWith('sentry-http-test', expect.any(Function));
112+
});
113+
114+
it('handleError method extracts an `HttpErrorResponse` with string', () => {
115+
const err = new HttpErrorResponse({ error: 'sentry-http-test' });
116+
createErrorHandler().handleError(err);
117+
118+
expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
119+
expect(captureExceptionSpy).toHaveBeenCalledWith(
120+
'Server returned code 0 with body "sentry-http-test"',
121+
expect.any(Function),
122+
);
123+
});
124+
});

0 commit comments

Comments
 (0)