Custom URL Shortener for Google Analytics using Rack

2010-06-13 20:00:00 -0400


A Rack-based URL Shortener for Link Tracking

If you’re like us and you like to send out a newsletter once in a while about one of your new products – in particular if you’re running promotional ad campaigns and your own marketing efforts – you’ve probably tried tacking on Google’s various utm_.* variables to the URLs you are sending people. These are really handy in that Google’s analytics code looks for these when users visit your website, allowing you to start tracking which of your ads, promos, and newsletters is performing well, and which are bombing.

The only problem with this is that you end up trying to put links like this into emails, and they’re long enough to be problematic on display, in particular in plain-text emails:

Write a blog post about Strip telling us what you like and
what you don't like, and we'll hook you up with a candy-
bar! Click here to learn more: (not an actual link)

We’ve all seen emails where that URL gets garbled and cut into two lines.

What we used to do for this was to employ a URL shortener like or, resulting in links like This solves the problem of munging, but brings on two new problems: your links are dependent on always being available and working correctly, and they’re not always easy to “fix” once they’re out there. In particular if you decide to change up the various UTM variables you’re sending to Google Analytics. PITA.

Suffice it to say, many sites are choosing to implement their own URL shorteners to ensure their carefully crafted links don’t rot, and we decided that we’d do ours as a tiny Rack application with a YAML file:

# Rack-up config
require 'rubygems'
require 'rack/zetetic/rack-campaign'

To install:

gem install rack-campaign

Here we list our campaigns out in YAML:

campaign: rack-campaign
source: blog
medium: internets
content: example link

campaign: rack-campaign
source: blog
medium: internets
term: strip rocks!
content: example link

At the top level of our Nginx config, we define an upstream directive for Unicorn, which will host our Rack application over a unix socket (mwahahaha):

# define upstream for campaign routing
upstream campaigns {
server unix:/www/campaigns/tmp/sockets/unicorn.sock;

Then, in each of our Nginx vhosts, we add a location directive for /c, telling the server to forward all such requests to the unicorn process:

location /c {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_max_temp_file_size 0;

# forward everything else to the mongrel cluster
if (!-f $request_filename) {
proxy_pass http://campaigns;

Now, we can pass out the link you may have seen on Twitter:


When you visit it, you get redirected to:

Isn’t that neat!?

This needs to perform very well, so we did a little testing with Apache’s ab testing tool and found it runs as fast as a normal nginx rewrite. Here’s what a basic rewrite looks like (/software rewrites to /code):

$ ab -n 500 -c 10 /software
Non-2xx responses: 500
Total transferred: 191000 bytes
HTML transferred: 92500 bytes
Requests per second: 4626.33 [#/sec] (mean)
Time per request: 2.162 [ms] (mean)
Time per request: 0.216 [ms] (mean, across all concurrent requests)
Transfer rate: 1721.00 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 2
66% 2
75% 2
80% 2
90% 2
95% 2
98% 2
99% 2
100% 2 (longest request)

And here’s how rack-campaign is performing:

$ ab -n 500 -c 10 /c/blog-rack-campaign
Non-2xx responses: 500
Total transferred: 167500 bytes
HTML transferred: 7000 bytes
Requests per second: 2749.78 [#/sec] (mean)
Time per request: 3.637 [ms] (mean)
Time per request: 0.364 [ms] (mean, across all concurrent requests)
Transfer rate: 896.43 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 2
66% 2
75% 3
80% 3
90% 4
95% 4
98% 13
99% 14
100% 39 (longest request)

Obviously, there’s some differences but so far, it looks like rack-campaign is doing well enough here. I’m sure it could be faster, and I’m sure that over time a YAML file could become a cumbersome manner of storage, but for now it’s good ’nuff. Feel free to fork it and use a SQLite database (which in this case would likely be very fast).


gem install rack-campaign

blog comments powered by Disqus