Log user's clicks without slowing the website

Posted by {"login"=>"Darwin", "email"=>"[email protected]", "display_name"=>"Darwin", "first_name"=>"Darwin"} on March 24, 2013

It is a common practice for websites to log clicks of a particular area or web page element to know better how does the visitors use the website, what they click most, what time and country they are clicking from and etc.
There are tons of paid and free services that does this functionality. One very common service is Google Analytics.

The problem though with this service is, you dont have the "raw" data of such traffic (http headers sent by the user agent). There is also no guarantee that all traffic is properly logged and the worst part of it is you are limited by what features the provider is willing to offer. One common problem is, if the area you are trying to observe is not a hyperlink (not made with anchor tag) but instead just some sort of div which redirects to some page when clicked, this is very hard to track in Google Analytics (although you can see it in heatmap, you wont really get that much detail as individual ip address, timestamp, user agent etc. for each clicks in that div).

Due to those drawbacks, webmasters commonly do some "quick scripts" to just "manually" log the clicks for a particular div or section (even hyperlinks). This could be as simple as capturing the whole $_SERVER variable and recording the REMOTE_ADDR, HTTP_HOST and other contents of the $_SERVER variable to either log file or MySQL database.

The problem with this approach though is, let's say a visitor clicked a div or link. Then you hook up some jQuery code around to perform a AJAX request to log the actual click:


In the sample above, the div could be any part of a web page that might have some fancy background image. What you could do to "log" all the clicks to this area is throw some jQuery codes:


$(function(){
   $('div.join-link').click(function(){
       var data-url = $(this).attr('data-target-url');
       $.ajax({
           type: 'POST',
           url: 'http://yourdomain.com/log_clicks.php',
           success:function(){window.location.href= data-url;},
           error:function(){window.location.href= data-url;}
       });
   });
})

What the jQuery code does is it initiates an asynchronous http request to http://yourdomain.com/log_clicks.php (the php file that does the actual logging) when any div with the class of join-link. After the codes of log_clicks.php had executed, the success or error function will be run which basically redirects the user to whatever url is specified in the data-target-url attribute of that div.

Now, let's see what the contents of log_clicks.php is:


As you could see in the script, it just basically captures the contents of $_SERVER variable and put it into a MySQL table. The problem with this approach though is, the JQuery code needs to wait for the script to finish before going to the target link. If the MySQL for some reason is slow, or the table where you want to insert the data is being bombarded with read/write operations (specially myisam formats), the execution time of log_clicks.php will be extremely slow. This might not be true if your website has only 100-200 simultaneous users, but when you have thousands of users clicking around your site, this will gonna put a lot of server load in the mysql database and eventually slow the perceived responsiveness of the links in your site.

One solution to this is to make the call to mysql database asynchronous -- that is, the log_clicks.php will return immediately without waiting for the data to be written. That way, the success/error function can run immediately and increase the responsiveness of the site even if the mysql server is slow in accepting the insert statement.

This can be done by writing another script that will sit between the log_clicks.php and mysql database, lets call it log_clicks_async.php


curl_post_async('log_click.php', array());

function curl_post_async($url, $params)
{
    foreach ($params as $key => &$val) {
      if (is_array($val)) $val = implode(',', $val);
        $post_params[] = $key.'='.urlencode($val);
    }
    $post_string = implode('&', $post_params);

    $parts=parse_url($url);

    $fp = fsockopen($parts['host'], 
        isset($parts['port'])?$parts['port']:80, 
        $errno, $errstr, 30);

    $out = "POST ".$parts['path']." HTTP/1.1\r\n";
    $out.= "Host: ".$parts['host']."\r\n";
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $out.= "Content-Length: ".strlen($post_string)."\r\n";
    $out.= "Connection: Close\r\n\r\n";
    if (isset($post_string)) $out.= $post_string;

    fwrite($fp, $out);
    fclose($fp);
}

What this code does is, it will post the data to log_clicks.php and return immediately w/o waiting for log_clicks.php to finish. That way, the redirect code in the jQuery code could execute immediately even if the MySQL server is very busy and is delaying the insert operation being done by log_clicks.php

Now, in the jQuery code, you'll just replace the log_clicks.php with log_clicks_async.php so that it calls the new file.

credit: curl_post_async is made by pete


Did you find this useful?

I'm always happy to help! You can show your support and appreciation by Buying me a coffee (I love coffee!).