Search code examples
ember.jsember-datasentryraven

Ember sentry/raven: set user context when user model loads?


% Preamble %

I am setting up ember-cli-sentry and am looking to identify the user so that when we get exceptions reported, we know what user those exceptions were associated with, in case they were user-specific. (Say, we deploy to staging, we got some data from the backend that was bad, causing certain errors with the only the users who got that bad data into their account).

& Actual question &

I want to execute some code immediately after the user model loads. I do not know what route is going to be triggering the user model to load.

In theory, actually, the earliest possible place I could get the user model data is immediately after the request for that data is completed. I wonder, would it be crazy to hijack the XHR method, and when the user id is requested, run my code to tell raven/sentry who this user is?

It would be better if there was simply a "loaded" hook for model files, but I've never heard of such a thing. Maybe this is the one use case where this would be desirable?

An h1 for google/seo:

Ember:Hook to execute code after model loads from any route? How do I execute code after model loads from any route?


Solution

  • Thank you @duizendnegen and @Isaac Askew, observers were a great starting point and led me to the final solution. This observer code, placed in my application route, helped me figure out exactly what I could add as user context:

    window.serviceState = {};
    // eslint-disable-next-line
    let serviceState = window.serviceState;
    
    export default Route.extend(ApplicationRouteMixin, {
      // observes services for state changes we care about and stores them in
      // a separate variable so when an exception occurs, we can simply include that
      // variable and avoid the risk of causing another exception by trying
      // to read them in `captureException`
      serviceStateTracker: observer(
        'service1.prop1',
        'service2.propA',
        function() {
          [
            'service1.prop1',
            'service2.propA',
          ].forEach((prop) => {
            let newValue = this.get(prop);
            if (serviceState[prop] !== newValue) {
              if (serviceState[prop] === undefined) {
                console.log(prop, newValue);
              } else {
                console.log(prop, 'changed from:', serviceState[prop], 'to:', newValue);
              }
              serviceState[prop] = newValue;
            }
          });
        }
      ),
    

    However, I did try just geting the props I care about in my logger service at exception time instead, and this worked well. It's better because you won't have code needlessly executing every time a property you want to log changes:

    // app/services/logr.js
    // initially generated with ember-cli-sentry's `ember g logger logr`
    import RavenLogger from 'ember-cli-deploy-sentry/services/raven';
    import Ember from 'ember';
    
    const { Logger, inject: { service } } = Ember;
    
    export default RavenLogger.extend({
    
      unhandledPromiseErrorMessage: '',
      user: service(),
      i18n: service(),
    
      addContext() {
        if (this == null) {
          // eslint-disable-next-line
          console.warn('logr cannot addContext because this is null/undefined, continuing without');
          return;
        }
        let tagsContext = {
          locale: this.get('i18n.locale'),
          id: this.get('user.id'),
          firstName: this.get('user.firstName'),
          lastName: this.get('user.lastName'),
          email: this.get('user.email')
        };
        // console.log('tagsContext', tagsContext);
        // For some reason setUserContext was not working, but setTagsContext was,
        // so I am using tags for all the user info. Maybe upgrading raven could fix this?
        this.callRaven('setTagsContext', tagsContext);
        // why use callRaven: https://github.com/damiencaselli/ember-cli-sentry/issues/58
      },
    
      // work around for unmerged PR:
      // https://github.com/damiencaselli/ember-cli-sentry/pull/97
      captureException(/* error */) {
        if (this.get('isRavenUsable')) {
          this.addContext();
          window.Raven.captureException(...arguments);
        } else {
          Logger.debug(...arguments);
        }
        return true;
      },
    
      captureMessage(/* message */) {
        if (this.get('isRavenUsable')) {
          this.addContext();
          window.Raven.captureMessage(...arguments);
        } else {
          Logger.debug(...arguments);
        }
        return true;
      }
    });
    

    For some reason the observer wouldn't work in my logr service..

    Extra notes:

    • with the ember-cli-deploy-sentry addon, don't forget to put THAT config in config/DEPLOY.js, not config/environment.js

    To trigger an error somewhere in some application route hook:

    setTimeout(() => {
      // I was having some issue with `throw new Error()` so I did this instead
      // It's actually just a better way to manually 'throw an exception'
      // because it will not forcibly crash the application, but instead
      // at least attempt to continue running
      this.get('logr').captureException(new Error('sentry user context test'));
    }, 10000);
    
    • Don't forget to set development: false to let raven actually send this test error to sentry (unless you aren't testing end to end) (I typically have this set: development: environment === 'development'

    In application route actions, add this to your error action handler:

    error(error) {
      // Trap unhandled failures during routing:
      // Should maybe be handled by the sentry addon, but is not as of now:
      // https://github.com/damiencaselli/ember-cli-sentry/issues/105
      this.get('logr').captureException(error);