Integrating TrackJS With Common JavaScript Frameworks

TrackJS comes with sensible defaults that will be effective and useful for all JavaScript applications. That said, there are a number of specific things you can do to integrate TrackJS specifically with various frameworks.

React/Redux

TrackJS works great with vanilla React applications out of the box. Depending on your build setup you may want to check out some of our build integrations for tips on initializing and configuring TrackJs.

However, if you’re using Redux, you can take things a step further. Redux provides excellent hooks to log the actions occurring on your site, and retrieve the state should something blow up. All you need to do is create a simple piece of Redux Middleware and apply it when creating your store:

// /middleware/trackJsLogger.js
const trackJsLogger = store => next => action => {
    try {
        // log every action so they appear in the TrackJS telemetry timeline
        console.log(action);
        return next(action)
    } catch (err) {
        // Something bad happened, let's log out the entire state so we can see it in the timeline
        console.warn(store.getState());

        // NOTE: this assumes trackJs was initialized previously, at app startup.
        // We automatically transmit on console.error()
        console.error(err)

        // Alternatively, if you'd rather not console.error()
        // You can explicitly track the event so it won't appear in the user's console.
        // if(trackJs) { trackJs.track(err) }
    }
}

export default trackJsLogger;

When you’re creating your store, just make sure you apply the middleware:

// /index.js
import { createStore, applyMiddleware } from 'redux'
import trackJsLogger from './middleware/trackJsLogger'

const store = createStore(todoApp, applyMiddleware(trackJsLogger));

And now, if something blows up, you’ll get great context!

React Error Boundaries

React 16 added the concept of “error boundary” components. These components are circuit breakers that catch errors bubbling up from child components. These are a great place to track errors and transmit them to TrackJS:

import React, { Component } from 'react';

/*global trackJs*/

export class ErrorBoundary extends Component {
    componentDidCatch(error, errorInfo) {
        if (errorInfo && errorInfo.componentStack) {
            // The component stack is sometimes useful in development mode
            // In production it can be somewhat obfuscated, so feel free to omit this line.
            console.log(errorInfo.componentStack);
        }

        // TrackJS should be configured and available before making this call
        // Either as an imported module or a global (ESLint global ignore is used above)
        trackJs.track(error);

        this.setState({ error });
    }

    render() {
        // Whatever rendering of error messages or children components you'd like
    }
}
Redux Dev Tools

It’s great to log the actions that occurred before an error. But what if you could play them back in Redux dev tools? Our awesome community has created a logger that will allow you to replay the actions in a new session!

Check out the TrackJS Redux Logger on Github.

Angular

Angular provides an $exceptionHandler decorator for capturing errors arising from your code. This example shows one way to plug TrackJS in to the $exceptionHandler.

angular.module("YOUR_APP", [])
  .config(["$provide", function ($provide) {

    $provide.decorator("$exceptionHandler", ["$delegate", "$window", function($delegate, $window) {
      return function (exception, cause) {
        if ($window.trackJs) {
          // Track error and log anything else of interest.
          $window.trackJs.track(exception);
        }

        // In *non-production* environments you may still want the error sent to the console.
        // You can delegate to the original handler (console.error) if you'd like.
        // Warning, this can cause double tracking of errors, so do not use in production.
        // $delegate(exception, cause);  
      };
    }]);

  }]);

Angular 2

Angular 2 exposes an ErrorHandler class that we can extend.

The first step is to create our new TrackJsErrorHandler.

// trackjs.handler.ts

import { ErrorHandler, Injectable } from '@angular/core';
declare var trackJs: any;

@Injectable()
export class TrackJsErrorHandler implements ErrorHandler {
  handleError(error:any) {
    // Add the error message to the telemetry timeline.
    // It can occasionally have useful additional context.
    console.warn(error.message);

    // Assumes we have already loaded and configured TrackJS*
    if (trackJs) {
      trackJs.track(error.originalError); // Send the native error object to TrackJS
    }
  }
}

Then we need to tell Angular 2 about our new error handler. When you specify an @NGModule you can list overrides for various providers.

// app.module.ts
import { AppComponent }  from './app.component';
import { ErrorHandler } from '@angular/core';
import { TrackJsErrorHandler } from './trackJs.handler';

@NgModule({
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ],
  // Tell Angular about our custom exception handler here
  providers: [{ provide: ErrorHandler, useClass: TrackJsErrorHandler }]
})
export class AppModule { }

*Note: You could conceivably configure and load TrackJS within the trackJs.handler.ts file, but you’ll miss any errors that occur before module initialization. Therefore we recommend loading TrackJS before module/application init, preferably as the first script on the page.

Vue

Vue has a global error handler where we can insert a call to trackJs.track(). Vue supplies a native JavaScript error object along with the Vue instance itself. From this we can gather component name, and additional data like properties.

  // Do this early during Vue application setup
  Vue.config.errorHandler = (err, vm, info) => {

    // Log properties passed to the component if there are any
    if(vm.$options.propsData) {
      console.log('Props passed to component', vm.$options.propsData);
    }

    // Emit component name and also the lifecycle hook the error occured in if present
    var infoMessage = `Error in component: <${vm.$options.name} />\n`;
    if(info){
      infoMessage += `Error occurred during lifecycle hook: ${info}\n`;
    }

    // This puts the additional error information in the Telemetry Timeline
    console.log(infoMessage);

    // Track the native JS error
    trackJs.track(err);
  }

Backbone

Backbone defines base types that you inherit from. We can use the watchAll API to automatically wrap up your Backbone functions.

// OPTION 1:
// Automatically wrap everything
if (!window.trackJs) return;

["View","Model","Collection","Router"].forEach(function(type) {
  var BackboneType = Backbone[type];
  Backbone[type] = BackboneType.extend({
    constructor: function() {
      // NOTE: This allows you to set _trackJs = false for any individual object
      //       that you want excluded from tracking
      if (typeof this._trackJs === "undefined") {
        this._trackJs = true;
      }

      if (this._trackJs) {
        // Additional parameters are excluded from watching. Constructors and Comparators
        // have a lot of edge-cases that are difficult to wrap so we'll ignore them.
        window.trackJs.watchAll(this, "model", "constructor", "comparator");
      }

      return BackboneType.prototype.constructor.apply(this, arguments);
    }
  });
});

// OPTION 2:
// Selectively wrap objects on instantiation.
var MyView = Backbone.View.extend({
  initialize: function () {
    if (window.trackJs) {
      window.trackJs.watchAll(this, "excludedFunction");
    }
  }
});
Backbone Marionette

Marionette extends Backbone with a ton of extra functionality for building large web applications. It follows the same extension patterns as Backbone, but a few of its base methods do not work well with wrapping.

// Automatically wrap everything in Marionette
if (!window.trackJs) return;

["View","Model","Collection","Router"].forEach(function(type) {
  var BackboneType = Backbone[type];
  Backbone[type] = BackboneType.extend({
    constructor: function() {
      // NOTE: This allows you to set _trackJs = false for any individual object
      //       that you want excluded from tracking
      if (typeof this._trackJs === "undefined") {
        this._trackJs = true;
      }

      if (this._trackJs) {
        // Additional parameters are excluded from watching. Constructors and Comparators
        // have a lot of edge-cases that are difficult to wrap so we'll ignore them.
        // childView and emptyView are specific to Marionette
        window.trackJs.watchAll(this, "model", "constructor", "comparator", "childView", "emptyView");
      }

      return BackboneType.prototype.constructor.apply(this, arguments);
    }
  });
});

Special thanks to Steve Willard, @brycekahle and @midhir for contributions!

Ember

Ember has a few different error handling integration points. The simplest is to just use the Ember.onerror callback. If you choose to also do some custom error handling in Ember.RSVP.onerror please be aware that the global Ember.onerror receives the same error data - so be careful not to double track!

Ember.onerror = function(error) {
  trackJs.track(error);
}
Ember CLI Integration

If you’re currently using Ember CLI and would like to integrate with TrackJS, you’re in luck! There is an actively maintained Ember CLI package that will get you up in running in no time!

Sencha/ExtJS

Sencha’s ExtJS is a popular commercial JavaScript framework for building web and mobile applications. ExtJS provides the Ext.Error.handle method for handling errors thrown via Ext.Error.raise. These built-in capabilities to capture detailed error information that makes integrating TrackJS easy.

// Log Ext JS errors to TrackJS
Ext.Error.handle = function(err) {
  if (window.trackJs) {
    var error = new Ext.Error(err);
    window.trackJs.track(error);
  }

  // In non-production environments still output to the console
  //<debug>
  Ext.log({
    msg: err.msg,
    level: "warn",
    dump: err,
    stack: true
  });
  //</debug>

  // Return true to not throw the error to the browser
  // Avoids error tracking duplication
  return true;
};

You can get even better errors by using the Observable methods to add extra details to the Telemetry Timeline. You can easily add it to some or all of your components.

// OPTION 1:
// Automatically instrument all components
// Note that this might impact your performance, but very useful for debugging.
var event = Ext.mixin.Observable.prototype.fireEvent;
Ext.mixin.Observable.prototype.fireEvent = Ext.Function.createInterceptor(event, function(msg, data) {
  if (window.trackJs) {
     // This can be customized as needed to output timeline data
    var cmpId = "";
    if (data && data.getId) {
        cmpId = data.getId();
    } else if (typeof data == "object" && data["getTarget"]) {
        cmpId = data.getTarget().id;
    } // Etc

    window.trackJs.console.info(msg + ': ' + cmpId);
    return true;
  }
});

// OPTION 2:
// Selectively instrument a single component
Ext.mixin.Observable.capture(Ext.getCmp("YOUR_COMPONENT_ID"),function(msg,cmp){
  if (window.trackJs) {
    window.trackJs.console.info(msg+": "+cmp.getId());
  }
});

Special thanks to the crew at OhmzTech for their contributions!

Meteor

By default TrackJS wraps calls to addEventListener in order to get better stack trace information. Meteor also comes out of the box with its own wrapping of addEventListener. If TrackJS is loaded after Meteor (which is common, given the way Meteor apps load) there is the potential that multiple events will be bound for the same user interaction.

For that reason, we recommend turning off TrackJS callback wrapping so it does not interfere with the Meteor event system.

window._trackJs = {
  token: "YOUR_TOKEN_HERE",
  callback: {
    enabled: false
  }
  // other configuration
}

Other Frameworks

There are more JavaScript frameworks out there than we can possibly cover. Many frameworks, however, require no special integration and will work well out of the box. If you have a framework you’d like to see specific instructions for, please email us at hello@trackjs.com.