Simple User Request Tracking Using Redis
Logging is an important part of any web application. Rails has a great logging mechanism, which is ideal to see what’s happening during development. It can also be useful in many other situations, but it has its limitations. Being text based, its output is meant to be human readable, but sometimes it’s better to keep track of requests in a way that’s readily available programmatically.
Although saving requests data into a SQL database using Active Record seems like a no-brainer, it’s not necessarily a good idea. As tracking requests can quickly become a write intensive operation, I’ve took the habit of using Redis for that purpose. It happen to be really easy to add basic tracking capability to your Rails application. We’ll look at how to accomplish this cleanly and easily.
We first need to create a non-AR-backed model (maybe we should start using the PORO acronym) using the Redis::Objects gem.
class Tracker include Redis::Objects list :requests, marshal: true, maxlength: 1000
We’ve defined a Redis-backed attribute called requests
which will act
as a normal Ruby list. The marshal
option tells Redis::Objects to
marshal the content of the list so that you can append any kind of Ruby
objects. The maxlength
option limit the number of items in the list so
that you don’t fill all available memory, but you still have to take
into account the number of user using your application.
The actual key that will be used inside Redis will be a combination of
three things: the name of the class, the name of the attribute and the
value returned by the id
method of the instance.
def initialize(user = nil) @user = user end def id @user.try(:id) || -1 end
Here we took an optional user
parameter in the initialize
method and
that user’s id
attribute will be used. If no user is given then we’ll
return -1 to represent anonymous users.
We then just need a method to actually perform the tracking. In this example we’ll just take the URL, the request’s parameters, the user’s IP and add a timestamp.
def track(request) requests << { url: request.url, params: request.params, ip: request.ip, time: Time.now } end
Now it’s a simple matter of adding a filter in the application controller.
before_filter :track unless Rails.env.test? # ... def track Tracker.new(current_user).track request end
Two things to note about the above snippet. Firstly, we are preventing
this filter to run in the test environment. Secondly, it assumes there’s
a current_user
method that will return nil
when no user is logged
in, so beware if you’re using Sorcery.
With the Tracker
model, we can easily get the last request made by a
user:
Tracker.new(current_user).requests.last