In part 1 we covered the three most common types of telemetry data we want to collect. In this part we will review how to actually implement tracking within your code.
For this example we are going to use Google Analytics. Simple, Free, and virtually ubiquitous. Of course you could send this information to another service or a custom back-end, but that’s beyond the scope of this discussion.
Google Analytics API
The three types of telemetry we want to track - page views, user actions and in-browser performance - all map very nicely to three calls in the Google Analytics API:
Although analytics.js gives you a means to send this information to the backend service, it’s not the sort of thing that we want littered all over our code. Thus…
Separation of Concerns
Before we start into the details, let’s talk application design for a moment. While you could simply litter the code with calls to the analytics API, that would make a mess, and be a huge pain should you want to change to use some other tracking system.
Instead, we want to centralize the tracking and storing of telemetry in a central service. The specifics depend on your framework (Backbone/Angular/Ember/Dojo/other) but you will likely have some sort of “global event bus”, or a core “Application” object which all elements of your application can access.
In our application, we are using Backbone and Marionette, and so we have added methods to our “Application” object, as that is passed into all the modules.
Tracking Page Views
Again, your application architecture will dictate where to attach these events, but in frameworks that have the concept of a router, that’s a good place to start. For all “navigation” actions in our application, follow the same pattern and centralize things onto an application method, specifically Application.navigate(). Super handy, because we can just drop in the page view logging in this one function as shown below:
The actual logPageView function is just a call to the analytics function.
Tracking User Actions
As we mentioned in the last post, this is simply a means to track what the user has interacted with. So, any place you have DOM event handlers, we want to assign that action a name, and make a call to Application.logUiEvent() method.
Depending on the amount of “magic” your application framework comes with (I’m looking at you Ember and Angular) this may be more or less difficult. Even with Marionette in the mix, our Backbone based app is pretty un-magical. All DOM interaction happens in handlers, defined in Views. So, all we do is drop in calls to logUiEvent in these handlers. While this could be made even more elegant by overriding the backbone and marionette view event binding infrastructure, in the interest of keeping the code easy to understand, we opted to just add these calls. Here is an example from one of our views.
Tracking In-Browser Performance
This is the trickiest of the bunch. As we mentioned, we need two calls - one to setup the timer, and a second to indicate the event we were tracking has completed. Or failed. We also need to handle the case where it does not complete.
For this we created a timer object - “Took.js” - which grabs a time stamp when it’s instantiated, and calculates the duration until the stop() method is called. We also have two additional methods - store() and log().
Here is a jsbin that you can play with as well (obviously it won’t report to Google Analytics)
We also expose this via the Application object as two simple methods startTimer(name, category, label, maxDuration) and stopTimer(name).
Through our code we wrap the various blocks we want timing data on in these two calls. Before we show an example, this brings up another area where we have centralized things - xhr calls. Although Backbone has a dependency on jQuery, and we could use $.ajax or $.getJson anywhere in the application, we have decided to route all requests through a central location - again on our Application object.
Anyhow - here is an example of a call that fetches rows from a remote feature service.
At this point, our project has not gone live, so we just have telemetry from dev and test environments in Google Analytics. That said, having this setup well before launch has already helped us re-arrange some of our naming conventions, and validated some ideas about the types of reports we can get out of the system.
Turns out that segmenting the data like this is not “built-in”, but it’s not hard to setup. Basically you create new “segments” and in the UI for that choose “Technology”, and then Browser & Browser Version.
With this in place, we can now easily compare performance of specific actions between different browsers. NOTE: data in this screen shot is from development - the actual performance is *much* better in production where all the code is combined and minified.