Modern JS app frameworks / libraries that follow the SPA paradigm (static content + JS app + JSON data via XHR) have changed the way you can track your users’ activities - monitoring HTTP requests to the server is not enough as client-side routing (like the one in Backbone.js or Angular.js) doesn’t have to make any actual request at all!
Why to bother with logging client-side events? What’s really important is the information that flies to the server as this is what gets persisted, isn’t it? In general - yes - totally agree, but:
- what if you want to collect some traces for user behavior analysis purpose (aka Web Analytics) or just simple troubleshooting?
- what about automated error / warning collection?
The problem is that sometimes you’d like to track really big number of events that come in irregular intervals (sometimes they fire every second or so, sometimes they don’t happen at all) - that doesn’t help with doing it efficiently.
Surely, you could either:
- send event details as metadata (like in request headers) with any other request that comes, but it’s troublesome (and sometimes your event is not related with any request)
- send event details in dedicated request once it (event) happens - but this way it can easily get out of control: 5 additional metadata requests for 1 meaningful functionality-related request? Doesn’t look good.
So, my idea is to buffer the events:
- Send the events at least once per X seconds, but …
- … send it earlier if you collect Y events before X seconds lapse, and …
- … if there are no events to be sent and X seconds passed, don’t send anything
It’s just the basic idea and it has its flows (so it needs some tuning):
- If browser is closed before X seconds lapsed or Y events happened, some events are lost.
- Events aren’t registered immediately,so if you’re gonna analyze them in real-time, that won’t really be “real-time”.
You can enhance the approach above with persisting the events in Local Storage and / or dumping the queue of events on “unload”, but for now, let’s get back the the main point: how to buffer the events in the simplest possible way? That’s how I PoCed it, using Angular.js and RxJS (http://reactive-extensions.github.io/RxJS/) - for the code, check the gist here - https://gist.github.com/liveweird/14e04592c05a7a746dd9:
- I catch my events at the level of $rootScope, so every module (I’ve built this into multi-module application) can send it without being coupled with the logging logic:
- What event handler does, is using a dedicated service to handle the actual logging:
- In Rx, the sequence if events is represented by Observable - there are plenty of flavors and you can create all of them with the helper methods, but in this case, the most convenient one is called Subject - it allows the manual control on what and when gets into the Observable:
- In a world more cruel and painful, I’d have to transform the stream of events manually (buffer it according to rules mentioned above by combining primitive event streams), but fortunately RxJS creaters have provided me with a correct method to the the suitable buffering for me. What it does (as a side effect) is changing the type of Observable from Observable of events to Observable of arrays of events.
- The only thing I’m missing is filtering the buffered events (actually arrays of events) that are empty - but it’s very simple:
- Observable is just one side of the coin as there’s still some piece of code that will actually log the buffered data to the server-side. It’s an Observer. What it does is to push every buffered event (again: array of actual events) using angular’s $http:
- To make it work, Observer has to subscribe to the created Observable.
Voila! It works.
Crucial points to remember about? Yes, some:
- You trigger the event using the standard angular syntax ($scope.emit)
- Remember that sending POSTs using $http or $resource can be b*tchy - it tries to send data encoded as json and it usually doesn’t work with the server-side. Check this article for details (and solution to the problem):http://t.co/7FzeIlEobP
That’s all folks! It’s that easy - if you have to mess with events on the client-side, do yourself a favor, use RxJS -> it will save you a lot of time and effort.