Skip to content

Commit 4cee445

Browse files
authored
ref: Better UnhandledRejection messages (getsentry#2185)
1 parent fc70752 commit 4cee445

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed

packages/browser/src/integrations/globalhandlers.ts

+68-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { getCurrentHub } from '@sentry/core';
2-
import { Event, Integration } from '@sentry/types';
3-
import { addExceptionTypeValue, isString, logger, normalize, truncate } from '@sentry/utils';
2+
import { Event, Integration, Severity } from '@sentry/types';
3+
import {
4+
addExceptionTypeValue,
5+
isPrimitive,
6+
isString,
7+
keysToEventMessage,
8+
logger,
9+
normalize,
10+
normalizeToSize,
11+
truncate,
12+
} from '@sentry/utils';
413

514
import { shouldIgnoreOnError } from '../helpers';
615
import { eventFromStacktrace } from '../parsers';
@@ -46,27 +55,13 @@ export class GlobalHandlers implements Integration {
4655
public setupOnce(): void {
4756
Error.stackTraceLimit = 50;
4857

49-
_subscribe((stack: TraceKitStackTrace, _: boolean, error: Error) => {
50-
// TODO: use stack.context to get a valuable information from TraceKit, eg.
51-
// [
52-
// 0: " })"
53-
// 1: ""
54-
// 2: " function foo () {"
55-
// 3: " Sentry.captureException('some error')"
56-
// 4: " Sentry.captureMessage('some message')"
57-
// 5: " throw 'foo'"
58-
// 6: " }"
59-
// 7: ""
60-
// 8: " function bar () {"
61-
// 9: " foo();"
62-
// 10: " }"
63-
// ]
58+
_subscribe((stack: TraceKitStackTrace, _: boolean, error: any) => {
6459
if (shouldIgnoreOnError()) {
6560
return;
6661
}
6762
const self = getCurrentHub().getIntegration(GlobalHandlers);
6863
if (self) {
69-
getCurrentHub().captureEvent(self._eventFromGlobalHandler(stack), {
64+
getCurrentHub().captureEvent(self._eventFromGlobalHandler(stack, error), {
7065
data: { stack },
7166
originalException: error,
7267
});
@@ -89,7 +84,7 @@ export class GlobalHandlers implements Integration {
8984
*
9085
* @param stacktrace TraceKitStackTrace to be converted to an Event.
9186
*/
92-
private _eventFromGlobalHandler(stacktrace: TraceKitStackTrace): Event {
87+
private _eventFromGlobalHandler(stacktrace: TraceKitStackTrace, error: any): Event {
9388
if (!isString(stacktrace.message) && stacktrace.mechanism !== 'onunhandledrejection') {
9489
// There are cases where stacktrace.message is an Event object
9590
// https://github.com/getsentry/sentry-javascript/issues/1949
@@ -98,6 +93,11 @@ export class GlobalHandlers implements Integration {
9893
stacktrace.message =
9994
message.error && isString(message.error.message) ? message.error.message : 'No error message';
10095
}
96+
97+
if (stacktrace.mechanism === 'onunhandledrejection' && stacktrace.incomplete) {
98+
return this._eventFromIncompleteRejection(stacktrace, error);
99+
}
100+
101101
const event = eventFromStacktrace(stacktrace);
102102

103103
const data: { [key: string]: string } = {
@@ -129,4 +129,53 @@ export class GlobalHandlers implements Integration {
129129

130130
return event;
131131
}
132+
133+
/**
134+
* This function creates an Event from an TraceKitStackTrace that has part of it missing.
135+
*
136+
* @param stacktrace TraceKitStackTrace to be converted to an Event.
137+
*/
138+
private _eventFromIncompleteRejection(stacktrace: TraceKitStackTrace, error: any): Event {
139+
const event: Event = {
140+
level: Severity.Error,
141+
};
142+
143+
if (isPrimitive(error)) {
144+
event.exception = {
145+
values: [
146+
{
147+
type: 'UnhandledRejection',
148+
value: `Non-Error promise rejection captured with value: ${error}`,
149+
},
150+
],
151+
};
152+
} else {
153+
event.exception = {
154+
values: [
155+
{
156+
type: 'UnhandledRejection',
157+
value: `Non-Error promise rejection captured with keys: ${keysToEventMessage(Object.keys(error).sort())}`,
158+
},
159+
],
160+
};
161+
event.extra = {
162+
__serialized__: normalizeToSize(error),
163+
};
164+
}
165+
166+
if (event.exception.values && event.exception.values[0]) {
167+
event.exception.values[0].mechanism = {
168+
data: {
169+
incomplete: true,
170+
mode: stacktrace.mode,
171+
...(stacktrace.message && { message: stacktrace.message }),
172+
...(stacktrace.name && { name: stacktrace.name }),
173+
},
174+
handled: false,
175+
type: stacktrace.mechanism,
176+
};
177+
}
178+
179+
return event;
180+
}
132181
}

packages/browser/src/tracekit.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// tslint:disable
22

3-
import { getGlobalObject, isError, isErrorEvent, normalize } from '@sentry/utils';
3+
import { getGlobalObject, isError, isErrorEvent } from '@sentry/utils';
44

55
/**
66
* @hidden
@@ -29,6 +29,7 @@ export interface StackTrace {
2929
stack: StackFrame[];
3030
useragent: string;
3131
original?: string;
32+
incomplete?: boolean;
3233
}
3334

3435
interface ComputeStackTrace {
@@ -271,12 +272,9 @@ TraceKit._report = (function reportModuleWrapper() {
271272
* @see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
272273
*/
273274
function _traceKitWindowOnUnhandledRejection(e: any) {
274-
var err = (e && (e.detail ? e.detail.reason : e.reason)) || e;
275+
var err = e && typeof e.reason !== 'undefined' ? e.reason : e;
275276
var stack = TraceKit._computeStackTrace(err);
276277
stack.mechanism = 'onunhandledrejection';
277-
if (!stack.message) {
278-
stack.message = JSON.stringify(normalize(err));
279-
}
280278
_notifyHandlers(stack, true, err);
281279
}
282280

packages/browser/test/integration/suites/builtins.js

+33-4
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ describe("wrapped built-ins", function() {
8080
}).then(function(summary) {
8181
if (summary.window.isChrome()) {
8282
// non-error rejections doesnt provide stacktraces so we can skip the assertion
83-
assert.equal(summary.events[0].exception.values[0].value, '"test"');
83+
assert.equal(
84+
summary.events[0].exception.values[0].value,
85+
"Non-Error promise rejection captured with value: test"
86+
);
8487
assert.equal(
8588
summary.events[0].exception.values[0].type,
8689
"UnhandledRejection"
@@ -93,6 +96,10 @@ describe("wrapped built-ins", function() {
9396
summary.events[0].exception.values[0].mechanism.type,
9497
"onunhandledrejection"
9598
);
99+
assert.equal(
100+
summary.events[0].exception.values[0].mechanism.data.incomplete,
101+
true
102+
);
96103
}
97104
});
98105
});
@@ -108,6 +115,10 @@ describe("wrapped built-ins", function() {
108115
if (summary.window.isChrome()) {
109116
// non-error rejections doesnt provide stacktraces so we can skip the assertion
110117
assert.equal(summary.events[0].exception.values[0].value.length, 253);
118+
assert.include(
119+
summary.events[0].exception.values[0].value,
120+
"Non-Error promise rejection captured with value: "
121+
);
111122
assert.equal(
112123
summary.events[0].exception.values[0].type,
113124
"UnhandledRejection"
@@ -120,21 +131,28 @@ describe("wrapped built-ins", function() {
120131
summary.events[0].exception.values[0].mechanism.type,
121132
"onunhandledrejection"
122133
);
134+
assert.equal(
135+
summary.events[0].exception.values[0].mechanism.data.incomplete,
136+
true
137+
);
123138
}
124139
});
125140
});
126141

127142
it("should capture unhandledrejection with an object", function() {
128143
return runInSandbox(sandbox, function() {
129144
if (isChrome()) {
130-
Promise.reject({ a: "b" });
145+
Promise.reject({ a: "b", b: "c", c: "d" });
131146
} else {
132147
window.resolveTest({ window: window });
133148
}
134149
}).then(function(summary) {
135150
if (summary.window.isChrome()) {
136151
// non-error rejections doesnt provide stacktraces so we can skip the assertion
137-
assert.equal(summary.events[0].exception.values[0].value, '{"a":"b"}');
152+
assert.equal(
153+
summary.events[0].exception.values[0].value,
154+
"Non-Error promise rejection captured with keys: a, b, c"
155+
);
138156
assert.equal(
139157
summary.events[0].exception.values[0].type,
140158
"UnhandledRejection"
@@ -147,6 +165,10 @@ describe("wrapped built-ins", function() {
147165
summary.events[0].exception.values[0].mechanism.type,
148166
"onunhandledrejection"
149167
);
168+
assert.equal(
169+
summary.events[0].exception.values[0].mechanism.data.incomplete,
170+
true
171+
);
150172
}
151173
});
152174
});
@@ -168,7 +190,10 @@ describe("wrapped built-ins", function() {
168190
}).then(function(summary) {
169191
if (summary.window.isChrome()) {
170192
// non-error rejections doesnt provide stacktraces so we can skip the assertion
171-
assert.equal(summary.events[0].exception.values[0].value.length, 253);
193+
assert.equal(
194+
summary.events[0].exception.values[0].value,
195+
"Non-Error promise rejection captured with keys: a, b, c, d, e"
196+
);
172197
assert.equal(
173198
summary.events[0].exception.values[0].type,
174199
"UnhandledRejection"
@@ -181,6 +206,10 @@ describe("wrapped built-ins", function() {
181206
summary.events[0].exception.values[0].mechanism.type,
182207
"onunhandledrejection"
183208
);
209+
assert.equal(
210+
summary.events[0].exception.values[0].mechanism.data.incomplete,
211+
true
212+
);
184213
}
185214
});
186215
});

packages/types/src/mechanism.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export interface Mechanism {
33
type: string;
44
handled: boolean;
55
data?: {
6-
[key: string]: string;
6+
[key: string]: string | boolean;
77
};
88
synthetic?: boolean;
99
}

0 commit comments

Comments
 (0)