question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Instrument incorrectly wrapping error causing sentry functions to fail

See original GitHub issue

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which package are you using?

@sentry/angular

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:closed
  • Created a year ago
  • Comments:23 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
Sammayecommented, Oct 3, 2022

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

1reaction
lobsterkatiecommented, Sep 13, 2022

Great, thanks, @Sammaye.

@Lms24, when you’re back, mind taking a look at this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Usage - Sentry Documentation
The captureMessage , captureException , context , and wrap functions all allow passing additional data to be tagged onto the error.
Read more >
Integrated error tracking compatibility with Sentry SDK - GitLab
The purpose of this issue is ensure that integrated error tracking works with all major Sentry SDK. Does it work? Related to #329596....
Read more >
Monitor Errors and Performance With Sentry - Learn With Jason
We set up Algolia on the front end to make some calls to our serverless functions in the back. There's a lot going...
Read more >
@sentry/types | Yarn - Package Manager
Important: This documentation covers modern versions of Yarn. For 1.x docs, see classic.yarnpkg.com. Yarn. ≡. Home · Getting started · Features.
Read more >
Porter Sentry Sedate User's Manual FM-1274
Porter Instrument recommends the user be thoroughly familiar with the use of Nitrous Oxide - Oxygen Conscious Sedation for patient analgesia and be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found