Instrument incorrectly wrapping error causing sentry functions to fail
See original GitHub issueIs there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which package are you using?
SDK Version
7.12.1
Framework Version
Angular 10.2.5
Link to Sentry event
https://sentry.io/organizations/delenta/issues/3583358067/?query=is%3Aunresolved
Steps to Reproduce
Used this in my main.ts:
if (environment.ENABLE_SENTRY) {
let dialogShownTimeout: any;
Sentry.init({
environment: environment.ENV,
dsn: "https://xxx",
integrations: [
new GlobalHandlers({
onerror: false,
onunhandledrejection: false,
}),
new BrowserTracing({
tracingOrigins: [
"localhost",
environment.API_SERVICE_BASE
],
routingInstrumentation: Sentry.routingInstrumentation,
}),
],
beforeSend(event, hint) {
// Check if it is an exception
if (event.exception) {
const userId = null;
const name = null;
const email =null;
if (userId) {
Sentry.setUser({
id: userId,
username: name,
email
});
}
event.fingerprint = [
'{{ default }}',
event.message ?? (
hint.originalException instanceof Error
? hint.originalException.message
: hint.originalException
),
event.request.url,
];
// Timeout ensures only the last dialog is sent if multiple errors are received
if (dialogShownTimeout) {
clearTimeout(dialogShownTimeout);
}
dialogShownTimeout = setTimeout(() => {
Sentry.showReportDialog({
eventId: event.event_id,
user: {
name,
email,
}
});
}, 250);
return html2canvas(document.body, {logging: false}).then(async (canvas) => {
const screenshotData = convertDataURIToBinary(canvas.toDataURL("image/jpeg", 0.5));
hint.attachments = [{filename: "screenshot.jpg", data: screenshotData}];
return event;
}).catch(() => {
// We failed to get a screenshot still give us error
return event;
});
}
},
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
ignoreErrors: [
// Random plugins/extensions
'top.GLOBALS',
// See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html
'originalCreateNotification',
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
'Can\'t find variable: ZiteReader',
'jigsaw is not defined',
'ComboSearch is not defined',
'http://loading.retry.widdit.com/',
'atomicFindClose',
// Facebook borked
'fb_xd_fragment',
// ISP "optimizing" proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha)
// See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy
'bmi_SafeAddOnload',
'EBCallBackMessageReceived',
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
'conduitPage',
// Generic error code from errors outside the security sandbox
// You can delete this if using raven.js > 1.0, which ignores these automatically.
'Script error.',
// Avast extension error
"_avast_submit",
// This seems to get thrown when we get errors in the error handler that are not constructed right
'Non-Error exception captured',
],
denyUrls: [
// Google Adsense
/pagead\/js/i,
// Facebook flakiness
/graph\.facebook\.com/i,
// Facebook blocked
/connect\.facebook\.net\/en_US\/all\.js/i,
// Woopra flakiness
/eatdifferent\.com\.woopra-ns\.com/i,
/static\.woopra\.com\/js\/woopra\.js/i,
// Chrome extensions
/extensions\//i,
/^chrome:\/\//i,
// Other plugins
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
/webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i
]
});
}
function convertDataURIToBinary(dataURI) {
var base64Index = dataURI.indexOf(';base64,') + ';base64,'.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(let i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
With a app module:
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
HttpClientJsonpModule,
AppRoutingModule,
SharedComponentModule,
],
providers: [
PreloadStrategy,
ConfigInit(),
{
provide: ErrorHandler,
useClass: GlobalErrorHandler
},
{
provide: Sentry.TraceService,
deps: [Router],
},
{
provide: APP_INITIALIZER,
useFactory: () => () => {},
deps: [Sentry.TraceService],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpInterceptorService,
multi: true
},
],
bootstrap: [AppComponent]
})
export class AppModule {
}
With a error handler like:
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error: Error | HttpErrorResponse): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
if (chunkFailedMessage.test(error.message)) {
if (navigator.onLine) {
// Only reload if the user is online, otherwise a message will show
// to tell them they are offline and the app will not work right
// We do not ask for confirmation for this since this is considered
// an unrecoverable state, the user is navigating, causing bundles to
// try to load, and they cannot progress since their cache is dead and
// the version they are seeking no longer exists
window.location.reload();
}
} else {
if (error instanceof HttpErrorResponse && !navigator.onLine) {
// we don't care abut errors while offline even if we could get them
return;
}
console.error(error);
const errorStatus = error instanceof HttpErrorResponse ? error.status : 0;
if (errorStatus === 0 || errorStatus >= 500) {
// This will log it to sentry and show an error message asking user to leave feedback
// You can find the configuration for this in the main.ts file
Sentry.captureException(this._extractError(error));
}
}
}
/**
* 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 {
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;
}
}
I used a function like:
this.dashboardService.getDashboardStatistic('dashboard').subscribe(statistics => {
const g = statistics.t.y;
this.statistic = <Statistic>statistics;
this.isDataLoaded = true;
});
Where by assignment of g would clearly raise an error.
Expected Result
Sentry would let the global error handler take this, as it works when I remove sentry
Actual Result
Sentry seems to throw this error somewhere else and avoid my global error handler altogether, I suspect the instrument.js and not only that but certain functions, while many others, do not work, like setUser(). In my main.ts everything there works but setUser when this happens, for every other error setUser works just fine.
This also worries me since I have a slightly custom error handler which checks for unrecoverable app state and does custom logic for that, I am concerned this could interfere with customer workflows if I release sentry to our live servers.
I suspect this to be a bug since it seems to break some of sentry’s own functions when it happens, such as setUser(), as I said.
Issue Analytics
- State:
- Created a year ago
- Comments:23 (9 by maintainers)
So the fact that it is could mean a deeper issue, ok I will try and few things a bit later and see if I can get any more insight
Great, thanks, @Sammaye.
@Lms24, when you’re back, mind taking a look at this?