Skip to content

Commit 006b096

Browse files
authored
fix(processing): Only mark aggregate errors as exception groups (#10850)
When Sentry started supporting the idea of exception groups, two changes happened. In the SDK, we adapted our logic for handling linked errors to also handle `AggregateError`s. And in our ingest pipeline, we began looking for an `is_exception_group` flag on the last entry in `event.exception.values; when we found it, we'd then ignore that entry when grouping and titling events, under the assumption that it was just a container and therefore wasn't meaningful. When it came to instances of `AggregateError`, this worked great. For linked errors, however, this caused us to focus on the `cause` error rather than the error which was actually caught, with the result that it both threw off grouping and made for some very unhelpful titling of issues. (See the screenshot below, in which the first three errors are, respectively, an `UndefinedResponseBodyError`, a `RequestError`, and an `InternalServerError`, though you'd be hard pressed to figure that out without opening them up.) This fixes those problems by restricting the use of the `is_exception_group` flag to `AggregateError`s. Note: In order to update the tests to work with this change, I had add in consideration of the error `name` property and the corresponding event `type` property, to match what we do in real life. To keep things readable, there's a new mock `AggregateError` class, which I adapted all the tests to use.
1 parent 95d2982 commit 006b096

File tree

2 files changed

+38
-16
lines changed

2 files changed

+38
-16
lines changed

packages/utils/src/aggregate-errors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ function aggregateExceptionsFromError(
5757

5858
let newExceptions = [...prevExceptions];
5959

60+
// Recursively call this function in order to walk down a chain of errors
6061
if (isInstanceOf(error[key], Error)) {
6162
applyExceptionGroupFieldsForParentException(exception, exceptionId);
6263
const newException = exceptionFromErrorImplementation(parser, error[key]);
@@ -106,7 +107,7 @@ function applyExceptionGroupFieldsForParentException(exception: Exception, excep
106107

107108
exception.mechanism = {
108109
...exception.mechanism,
109-
is_exception_group: true,
110+
...(exception.type === 'AggregateError' && { is_exception_group: true }),
110111
exception_id: exceptionId,
111112
};
112113
}

packages/utils/test/aggregate-errors.test.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ import { applyAggregateErrorsToEvent, createStackParser } from '../src/index';
44

55
const stackParser = createStackParser([0, line => ({ filename: line })]);
66
const exceptionFromError = (_stackParser: StackParser, ex: Error): Exception => {
7-
return { value: ex.message, mechanism: { type: 'instrument', handled: true } };
7+
return { value: ex.message, type: ex.name, mechanism: { type: 'instrument', handled: true } };
88
};
9+
class FakeAggregateError extends Error {
10+
public errors: Error[];
11+
12+
constructor(errors: Error[], message: string) {
13+
super(message);
14+
this.errors = errors;
15+
this.name = 'AggregateError';
16+
}
17+
}
918

1019
describe('applyAggregateErrorsToEvent()', () => {
1120
test('should not do anything if event does not contain an exception', () => {
@@ -57,6 +66,7 @@ describe('applyAggregateErrorsToEvent()', () => {
5766
exception: {
5867
values: [
5968
{
69+
type: 'Error',
6070
value: 'Nested Error 2',
6171
mechanism: {
6272
exception_id: 2,
@@ -67,22 +77,22 @@ describe('applyAggregateErrorsToEvent()', () => {
6777
},
6878
},
6979
{
80+
type: 'Error',
7081
value: 'Nested Error 1',
7182
mechanism: {
7283
exception_id: 1,
7384
handled: true,
7485
parent_id: 0,
75-
is_exception_group: true,
7686
source: 'cause',
7787
type: 'chained',
7888
},
7989
},
8090
{
91+
type: 'Error',
8192
value: 'Root Error',
8293
mechanism: {
8394
exception_id: 0,
8495
handled: true,
85-
is_exception_group: true,
8696
type: 'instrument',
8797
},
8898
},
@@ -123,19 +133,21 @@ describe('applyAggregateErrorsToEvent()', () => {
123133

124134
// Last exception in list should be the root exception
125135
expect(event.exception?.values?.[event.exception?.values.length - 1]).toStrictEqual({
136+
type: 'Error',
126137
value: 'Root Error',
127138
mechanism: {
128139
exception_id: 0,
129140
handled: true,
130-
is_exception_group: true,
131141
type: 'instrument',
132142
},
133143
});
134144
});
135145

136146
test('should keep the original mechanism type for the root exception', () => {
137-
const fakeAggregateError: ExtendedError = new Error('Root Error');
138-
fakeAggregateError.errors = [new Error('Nested Error 1'), new Error('Nested Error 2')];
147+
const fakeAggregateError = new FakeAggregateError(
148+
[new Error('Nested Error 1'), new Error('Nested Error 2')],
149+
'Root Error',
150+
);
139151

140152
const event: Event = { exception: { values: [exceptionFromError(stackParser, fakeAggregateError)] } };
141153
const eventHint: EventHint = { originalException: fakeAggregateError };
@@ -147,10 +159,12 @@ describe('applyAggregateErrorsToEvent()', () => {
147159
test('should recursively walk mixed errors (Aggregate errors and based on `key`)', () => {
148160
const chainedError: ExtendedError = new Error('Nested Error 3');
149161
chainedError.cause = new Error('Nested Error 4');
150-
const fakeAggregateError2: ExtendedError = new Error('AggregateError2');
151-
fakeAggregateError2.errors = [new Error('Nested Error 2'), chainedError];
152-
const fakeAggregateError1: ExtendedError = new Error('AggregateError1');
153-
fakeAggregateError1.errors = [new Error('Nested Error 1'), fakeAggregateError2];
162+
163+
const fakeAggregateError2 = new FakeAggregateError([new Error('Nested Error 2'), chainedError], 'AggregateError2');
164+
const fakeAggregateError1 = new FakeAggregateError(
165+
[new Error('Nested Error 1'), fakeAggregateError2],
166+
'AggregateError1',
167+
);
154168

155169
const event: Event = { exception: { values: [exceptionFromError(stackParser, fakeAggregateError1)] } };
156170
const eventHint: EventHint = { originalException: fakeAggregateError1 };
@@ -167,17 +181,18 @@ describe('applyAggregateErrorsToEvent()', () => {
167181
source: 'cause',
168182
type: 'chained',
169183
},
184+
type: 'Error',
170185
value: 'Nested Error 4',
171186
},
172187
{
173188
mechanism: {
174189
exception_id: 4,
175190
handled: true,
176-
is_exception_group: true,
177191
parent_id: 2,
178192
source: 'errors[1]',
179193
type: 'chained',
180194
},
195+
type: 'Error',
181196
value: 'Nested Error 3',
182197
},
183198
{
@@ -188,6 +203,7 @@ describe('applyAggregateErrorsToEvent()', () => {
188203
source: 'errors[0]',
189204
type: 'chained',
190205
},
206+
type: 'Error',
191207
value: 'Nested Error 2',
192208
},
193209
{
@@ -199,6 +215,7 @@ describe('applyAggregateErrorsToEvent()', () => {
199215
source: 'errors[1]',
200216
type: 'chained',
201217
},
218+
type: 'AggregateError',
202219
value: 'AggregateError2',
203220
},
204221
{
@@ -209,6 +226,7 @@ describe('applyAggregateErrorsToEvent()', () => {
209226
source: 'errors[0]',
210227
type: 'chained',
211228
},
229+
type: 'Error',
212230
value: 'Nested Error 1',
213231
},
214232
{
@@ -218,6 +236,7 @@ describe('applyAggregateErrorsToEvent()', () => {
218236
is_exception_group: true,
219237
type: 'instrument',
220238
},
239+
type: 'AggregateError',
221240
value: 'AggregateError1',
222241
},
223242
],
@@ -239,6 +258,7 @@ describe('applyAggregateErrorsToEvent()', () => {
239258
exception: {
240259
values: [
241260
{
261+
type: 'Error',
242262
value: 'Nested Error 2',
243263
mechanism: {
244264
exception_id: 2,
@@ -249,22 +269,22 @@ describe('applyAggregateErrorsToEvent()', () => {
249269
},
250270
},
251271
{
272+
type: 'Error',
252273
value: 'Nested Error 1',
253274
mechanism: {
254275
exception_id: 1,
255276
handled: true,
256277
parent_id: 0,
257-
is_exception_group: true,
258278
source: 'cause',
259279
type: 'chained',
260280
},
261281
},
262282
{
283+
type: 'Error',
263284
value: 'Root Error',
264285
mechanism: {
265286
exception_id: 0,
266287
handled: true,
267-
is_exception_group: true,
268288
type: 'instrument',
269289
},
270290
},
@@ -287,6 +307,7 @@ describe('applyAggregateErrorsToEvent()', () => {
287307
exception: {
288308
values: [
289309
{
310+
type: 'Error',
290311
value: 'Nested Error 2 ...',
291312
mechanism: {
292313
exception_id: 2,
@@ -297,22 +318,22 @@ describe('applyAggregateErrorsToEvent()', () => {
297318
},
298319
},
299320
{
321+
type: 'Error',
300322
value: 'Nested Error 1 ...',
301323
mechanism: {
302324
exception_id: 1,
303325
handled: true,
304326
parent_id: 0,
305-
is_exception_group: true,
306327
source: 'cause',
307328
type: 'chained',
308329
},
309330
},
310331
{
332+
type: 'Error',
311333
value: 'Root Error with...',
312334
mechanism: {
313335
exception_id: 0,
314336
handled: true,
315-
is_exception_group: true,
316337
type: 'instrument',
317338
},
318339
},

0 commit comments

Comments
 (0)