Monitor your Ember Application in Production

Why monitoring?

When you see that...

Recipe of an Ember App

  1. Write code for months
  2. Only look at it in Google Chrome (canary)
  3. Think very hard your app is unbreakable
  4. Get wasted the day before release
  5. Release
  6. Take a week off

Everything is great.

Until that email...

Jill

Hello! 

I am trying to fill in your membership web page but am having problems with my address being accepted into this page . 

I am from New Zealand.

Thanks!

Jill

Support (me)

Hi Jill, 


Could you send me a screenshot of the page you are having issues with?

Thanks, 
Pedro 

Jill

Hi Pedro, 

Thank you for your quick response. I'm not that computer literate but with some assistance from daughter may be able to send a screen shot.

 

Thanks,

Jill

Jill

Hi Pedro,

Just tried this again. The actual message that pops up after filling in all mandatory fields is "an error occurred while processing your submission".

Does that help you? daughter not around at present.

 

Regards,

Jill  

Hi Jill,

Thanks for the screenshots.

  1. Click the 'Continue' button to get the error message "an error occurred while processing your submission"
  2. Hit the F12 key on your keyboard, a little window will appear at the bottom of the screen
  3. Take a screenshot or copy paste what you see under the Console tab (errors appear in red)

May I ask you one more favor so I can help you?

Maybe you could ask your daughter to help you out?

Please follow these steps:

Jill?

How to track errors in Ember?

Catch 'Em All

// Global error handler in Ember run loop
Ember.onerror = function (err) {
    console.log(err);
};

// Global error handler for promises
Ember.RSVP.on('error', function(err) {
    console.log(err);
});

By putting this code in an initializer

Do not forget...

// Catch rejections in beforeModel, model and afterModel hooks
export default Ember.Route.extend({
    actions: {
        error: function (err, transition) {
            console.log(err);
            return true;
        }
    }
});

// For those who are paranoid
window.onerror = function (err) {
    console.log(err);
}

Scenario: saving a user

user.save().then(function () {
    // all went well
}).catch(function (err) {
    // OMG I have an error, what do I do?
});

Solution 1: let it bubble up!

user.save().then(function () {
    // all went well
});
// Global error handler for promises
Ember.RSVP.on('error', function(err) {
    console.log(err);
});

It will be handled by your global error handler:

Solution 2: handle it locally

user.save().then(function () {
    // all went well
}).catch(function (err) {
    if (err && err.status === 409) { 
        // Expected rejection, inform user and swallow error
        alertify.error('Email address already registered.');
    } else {
        // Bubble up to global error handler
        throw err;
    }
});

Then bubble up?

So what about monitoring?

Alternatives

  1. Log errors to your own infrastructure
  2. Log errors to an external service

1. Home-made logging

export default {
    name: 'error-monitoring',

    initialize: function (container, app) {
        Ember.onerror = function (err) {
            this.logError(err);
        };
        Ember.RSVP.on('error', function(err) {
            this.logError(err);
        });
    }

    logError: function (err) {
        Ember.$.ajax('/errors', {
            type: 'POST',
            data: {
                stack: err.stack,
                otherInformation: 'exception message'
            }
        });
    }
};

1. Home-made logging

  • Pros
    • Cheap
    • Customizable
  • Cons
    • What a waste of time
    • Will likely blow up when you need it the most

2. Error logging services

They all want your money errors!

Example with {track:js}

export default {
    name: 'error-monitoring',

    initialize: function (container, app) {
        Ember.onerror = function (err) {
            this.logError(err);
        };
        Ember.RSVP.on('error', function(err) {
            this.logError(err);
        });
    }

    logError: function (err) {
        // Log in trackJs (prod only)
        trackJs.track(err);

        // Log to console (dev only)
        Ember.Logger.assert(false, err);
    }
};

Timeline in {track:js} 

How to find Jill?

export default Ember.Route.extend(ApplicationRouteMixin, {
    setTrackJsUser: function (userId) {
        trackJs.configure({
            userId: userId.toString()
        });
    },
    actions: {
        sessionAuthenticationSucceeded: function () {
            this.setTrackJsUser(this.get('session.user.id'));
        }
    }
});

How to find Jill?

Other sweet feature

Enable sourcemaps for production builds

// In your Brocfile.js
var app = new EmberApp({
    sourcemaps: {
        enabled: true,
        extensions: ['js']
    }
});

Couple gotchas

_convertToError: function (err) {
    if (Ember.typeOf(err) === 'object') {
        var msg = err.responseText || err.message || err.toString();
        var status = err.status;
        err = new Error(msg);
        if (status) {
            err.status = status;
        }
    }
    return err;
},
logError: function(err) {

    // Ensure that error is an instance of Error
    err = this._convertToError(err);

    // Log in trackJs and console
    trackJs.track(err);
    Ember.Logger.assert(false, err);
}

Gotcha #1: most Ember errors are not instances of Error

Couple gotchas

<script>
    window._trackJs = {
        token: 'baguetteandcheese',
        // Whether to send errors to TrackJS
        enabled: true,
        network: {
            // Do not log all XHR errors (creates duplicates)
            enabled: false
        }
    };
</script>

Gotcha #2: {track:js} logs all XHR errors by default

Couple gotchas

import request from 'ic-ajax';

export default Ember.Controller.extend({
    actions: {
        resetPassword: function () {
            request({
                type: 'POST',
                url: 'users/reset-password',
                data: {
                    email: this.get('emailAddress')
                }
            }).then(function () {
                self.transitionToRoute('login');
            }).catch(function (err) {
                err = err.jqXHR || err;
                if (err.status === 404) {
                    alertify.error('User not found.');
                } else {
                    throw err;
                }
            });
        }
    }
});

Gotcha #3: if you disable XHR logging, then you should use promises

I'd love to offer the meetup something. You can share that if they email todd@trackjs.com that they saw you at the Ember JS Meetup, we'll give them 3 months for free.

Todd H Gardner
President and CoFounder TrackJS

Thanks!

Beers and trolls will be served on the rooftop.

Error Handling and Monitoring with Ember.js

By Guillaume Zurbach

Error Handling and Monitoring with Ember.js

Monitor your Ember Application in Production

  • 4,078