Blog

  • Handy dataLayer debugging tool for Chrome

    One of those posts that were laying around on my drafts and that I’ve decided to publish. I know there’re out there some good and well stablished tools that helps you to debug your Google Tag Manager implementation like the dataSlayer extension. But in the previous months I’ve been using a little JavaScript snippet that allows me to view in easy way the pushes that are being sent to Google Analytics in real-time in a nice way:

    This is how the debugging will looks like:

    tool

    It works for Chrome +36, and it’s based on a few lines of code, to start using it you’ll just need to paste those lines into your browser console, and you’ll start seeing any new dataLayer pushes that are sent for the current loaded page:

    Object.observe(dataLayer, function(c) {
        for(i=0;i<c.length;i++)
        {
    	if(c[i].type=="add")
    	{
                console.log("%cNew dataLayer Push\n", "color: blue; font-size:15px;");
    	    console.log(JSON.stringify(c[i].object[c[i].name], null, '\t'));
    	}
        }
    });
    

    You can grab the code too from this gitHub repository: https://github.com/thyngster/universalanalytics/blob/master/datalayer_monitor.js

    A nice way to have this little debugger at hand is to add a new bookmark in Chrome and set the URL to this piece of code. From then on, clicking on the bookmark will enable the debugging for the current loaded page

    javascript: (function() {
        Object.observe(dataLayer, function(c) {
            for (i = 0; i < c.length; i++) {
                if (c[i].type == "add") {
                    console.log("%cNew dataLayer Push\n", "color: blue; font-size:15px;");
                    console.log(JSON.stringify(c[i].object[c[i].name], null, '\t'));
                }
            }
        });
    })();
    
    bookmark

    Not the best debugging tool, but pretty handy, hope someone finds it useful at some point.

  • How to track an intranet or an hybrid app

    As we all know (do we?) Google Analytics uses cookies in order to work. This is, if for some reason the cookie can’t be set it won’t give any error but hits won’t be fired at all. A cookie is usually set for a FQHN (Fully Qualified Host Name). So if for example we’re going to track our intranet and we access it using an URL like : http://intranet/ Google Analytics is likely not going to work (it will depend on the browser that is accessing the page.

    There’s another situation where we may have problems using the official analytics.js. For example when using a different protocol than http/https, an example of this if when we load an html file from the local file system ( file://index.html ). You may think this is not a usual way to access a webpage, but it’s the way the most Hybrid Apps work.
    A native app for a mobile is usually build using some wellknown web technologies like: HTML5, JavaScript and CSS, and uses a browser engine (not the browser itself) to load the main content locally (using the file:// protocol), and here we’ll hit the second issue, Universal Analytics has an internal task named checkProtocolTask, it basically aborts the request if the current protocol does match http/https.

    Another example of devices using html files loaded from the local filesystem will be the SmartTV’s  for example.

    Fortunately Universal Analytics gives us the possibility to change those behaviours in order to manage those situations.

    Tracking a non FQHN domain name

    Even if it may work for some domains we better assure that it will run for all browsers, for this we need to set cookie’s domain to ‘none’ ,

    ga('create', 'UA-XXXXXXX-YYY','none'});
    

    or

    ga('create', 'UA-XXXXXXX-YYY', {'cookieDomain': 'none'});

    Tracking a locally loaded page

    In this case we’ll need to set the storage engine to none, and disabling the protocol check:

       ga('create', 'UA-123123123-123',{
          'storage': 'none', 
          'clientId':'{{UUIDv4}}'
      });
      ga('set', 'checkProtocolTask', null);
      ga('set', 'appName', 'MyApp Name');
      ga('set', 'appVersion', '1.2.2');
      ga('send', 'appview', {
            'screenName': 'My Home Screen'
      });
    

    Let’s take to the 2 important line in the code above;

    'storage': 'none'

    This line disables the default cookie storage system, and set’s it to none.

     'clientId':'{{UUIDv4}}'

    As we have disabled the storage engine, we’ll need to manually set the clientId (the cid parameter within our hits) , usually most devices offers a way to gan Unique Identifier, or we could grab the mac address and convert it to a UUIDv4 value πŸ™‚

      ga('set', 'checkProtocolTask', null);

    This last line disables the protocol checking task, so the requests are not aborted because it doesn’t match the http/https protocols.

  • Learn how does Universal Analytics Cross Domain work

    Univeral Analytics tracking is based on cookies and on a randomly generated clientId. Yep, that’s it. Each time you visit a site using Google Analytics for the first time a new cookie is set in your browser with a randomly generated hash (clientId), that will be used by Google Analytics servers to identificate you in the subsecuent visits.

    Let’s see how the cookie looks:

    crossdomain_universal

    Any of those values can be modified using the JavaScript API, and they need to be set within the tracker creation. In a 99.99999% of cases you won’t need to deal with this, but it’s always good to know that we have the possibility to define our own vales. Let’s see how a default tag would work if we were setting it manually:

    ga('create', 'UA-XXXXXXX-YYY', {
      'cookieName': '_ga',
      'cookieDomain': 'thyngster.com',
      'cookieExpires': 60 * 60 * 24 * 730  // 2 years expiration date
    });>

    As we can see in the previous screenshot Google Analytics does not just use the randomly generated hash to track the user (device), as it only is a 10 digits number, so in a high traffic site or even if not having good luck it could end in generaeting clientIds collitions.  To reduce the chance of having a duplicated clientId under our property. To address this problem Google Analytics add the cookie creation timestamp value to the clientId value instead of only the random hash. If our “_ga” cookie value is: “GA1.2.2055842233.1422407281” , the clientId would be: “2055842233.1422407281” and not just “2055842233”.

    Going back to the crossdomain tracking,  Google Analytics uses the clientId (cid parameter on the hits) to identify the users (devices), and it will make sense to think that when the user goes to a different domain the same clientId should be used on the destination domain. In order to be able to have the same clientId in 2 different domains we’ll need to pass the clientId value to the  destination domain, that way analytics.js will be able to use that clientId value for the destination domain instead of generating a new one (remember that ga cookies uses 1st party cookies).

    The JavaScript tracking API does provide some in-build methods to grab the linker parameters, or even to “decorate” the links to the “external” domain we define.

    ga(function(tracker) {
    var linkerParams = tracker.get('linkerParam');
    });
    
    crossdomain_universal_2

    You can see that all info returned by the linker makes sense, but there’s a new value that is not in our origin domain. What it is?. It’s a value that Universal Analytics uses to assure the cookie data integrity and to prevent in some way to have our cookie info hijacked. Using the linker parameter into a domain name using Universal Analytics with the allow linker option enabled will force the cookie override with the cookie values specified in the url, and we don’t to have anything sharing a link to our site in some social network and therefore messing up all our data because all users coming from that link have the same clientId do we?

    In the previous Google Analytics versions, the only not randomly generated hash that was being used was the domainhash ( first value in the __utma cookie). It was generated by a pretty simple  algorithm (click here to see a PHP port for the domainhash algo) .

    This time Google Analytics want further and uses al algorithm that take some more variables in mind, and most of them are not fixed values as it was the hostname.

    The current checksum hash uses the following infos:
    • The current clientId value
    • The current browser user agent
    • The current browsers timezoneoffset ( the difference in minutes with the UTC time )
    • All the descriptions from the available plugins within the current user’s browser (taken from window.navigator.plugins)
    • Current year, day hour and minute

    Below you can see the values used to generate the checksum for the crossdomain linking on my Firefox Browser:

    crossdomain_universal_3
    So at this point we’ll have an array with all those values, Next step analytics.js takes it to join all those array values using a dot, forming the following string/hash:
    crossdomain_universal_4
    Universal Analytics uses then this hashing algorithm (taken from analytics.js code) to convert the previous string into a numeric value (yep, you guessed it right, the one that was in our linker parameters value)
        function La(a) {
            var b = 1,
                c = 0,
                d;
            if (a)
                for (b = 0, d = a[y] - 1; 0 <= d; d--) c = a.charCodeAt(d), b = (b << 6 & 268435455) + c + (c << 14), c = b & 266338304, b = 0 != c ? b ^ c >> 21 : b;
            return b
        };
    

    When implemeting a crossdomain tracking the generated hash on the destion domain NEEDS to match with the current one generated when creating the tracker with the clientId data in the URL If they don’t match the cookie value won’t be overrided, and therefore crossdomain linking will fail and the cookie value will be created with a newly random clientId.

    Those are the points we need to take in mind when thinking in the crossdomain tracking:

    • The linker value is only valid when using the same navigator version and using the same plugins (same browser needs to visit the origin and destination domain).
    • The crossdomain tracking won’t work if we land on the destination domain later than 2 minutes after the linker value has been generated. ie: the linker value has to be generated on the fly when the user clicks on the destination domain, if we update the links on the page load and an user clicks on the destionation domain after the default time limit it won’t work. According to Google’s documentation, there’s a grace period of 2 minutes for the linker value. And that’s why Universal Analytics provides the decorate function, this function generates the linker value when user clicks on a link or either when a form is submitted, without the need of coding any javascript listeners to update the links value in real time when the user clicks on them.
    • The clientId used when generating the hash needs to match the one on the linker parameters, so even if having a valid hash for the linking, changing the clientId values won’t be possible.
    • User’s may not change the computer’s timezone configured while going to the destination domain.

    This is how the crossdomain linking works on Universal Analytics and how Universal analytics tries to keep the cookie data integrity and prevent in some way the Universal Analytics cookie hijacking.

  • How does the Universal Analytics snippet work

      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    
      ga('create', 'UA-123123123', 'auto');
      ga('send', 'pageview');
    
    

    The first thing that may attract your attention is the function parameters, they look like a real word. Let start talking about what an “isogram” is, according to the wikipedia:

    An isogram (also known as a “nonpattern word”) is a logological term for a word or phrase without a repeating letter.

    i,s,o,g,r,a,m are the variables names being used in the Universal Analytics Tracking Code, and we don’t want to pass a duplicate variable name to it. So actually this is just a pun from the Google guys since any other word with not repeated letters could have been used.

    You may wondering what values are being passed to the function, and to know that we’ll need to look to the bottom of the Universal Analytics Tracking Code:

    (function(i,s,o,g,r,a,m){ ... }
    )(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    

    This means “window” variable is being passed as the “i” parameter, “document” as the “s” parameter and so on.

    At his point we could translate the Universal Analytics Tracking Code as:

          window['GoogleAnalyticsObject'] = 'ga';
          window['ga'] = window['ga'] || function() {
              (window['ga'].q = window['ga'].q || []).push(arguments)
          }, 
          window['ga'].l = 1 * new Date();
          a = document.createElement('script'),
          m = document.getElementsByTagName('script')[0];
          a.async = 1;
          a.src = '//www.google-analytics.com/analytics.js';
          m.parentNode.insertBefore(a, m)
    

    Let’s look to the code line by line to see what it does:

    window['GoogleAnalyticsObject'] = 'ga';

    It creates a new global variable named “GoogleAnalyticsObject” that holds the main Universal Analytics object name.

    window['ga'] = window['ga'] || function() {
              (window['ga'].q = window['ga'].q || []).push(arguments)
    }
    

    Here the main ga object is created. If it is already defined then nothing happens, otherwise it will be declared as a function.
    This function will create a temporary window[‘ga’].q array and then push all the arguments that we pass to the function to that temporary array ( ga(‘value’,value’); ). It’s done this way as it may  happen that the very first ga(); calls may come before UATC has been executed. It saves them in that array and after the code has been executed it will process them. From this point on, subsecuent calls will be directly processed.

    window['ga'].l = 1 * new Date();

    This sets a new object property with the current timestamp value. Yes, multipling a javaScript Date object by 1 returns the current Date variable timestamp value.

    a = document.createElement('script')

    A new a new script element is created and saved into a variable named ‘a’ . This is what what will be injected into the page’s DOM, but not before some other things are set.

    m = document.getElementsByTagName('script')[0]

    Here the code looks for the first ‘script’ tag available on the DOM at the runtime, This will be used to attach the Universal Analytics tag before it.

    a.async = 1

    Sets the asynchronous attribute to our script tag, this will ensure the script will be run at the first opportunity after the script source is downloaded and before the window.load event without blocking the page parsing. Some browsers support the “defer” attribute which is the same as “async” but it will comply with the scripts order, again without blocking the page load.

    a.src = '//www.google-analytics.com/analytics.js'

    Now the src attribute is set for the script tag. This is from where the UATC is loaded. As you may have noticed the Schema is not specified, so it will be loaded from the current loading page schema, that is, if the page is loaded from https, the script will loaded from https://www.google-analytics.com and if the page is not under https, it will be loaded from http://www.google-analytics.com

    m.parentNode.insertBefore(a, m)

    Here is where the Universal Analytics Tracking Script is being injected into the page’s DOM, The first ‘script’ found on the page is being passed, looking for its parentNode, and then injecting the Tracking Code right before it.

    So this is basically how the UATC code works. I’m aware that my english on this post may not be the best, so any improvement over my explanations will be welcomed.

    Note: Simo-san just adviced me about this post that talks exactly about the same topic: http://code.stephenmorley.org/javascript/understanding-the-google-analytics-tracking-code/ , so you may want to check it too πŸ™‚

    Thanks to Ranyere Rodrigues for helping me to improve the post with some english corrections πŸ™‚

  • Pushing data to the right Google Tag Manager dataLayer namespace

    It may happen that the site we’re tracking is using a custom dataLayer name, or that it’s using more than one Google Tag Manager accounts at the same time and therefore (it should) using 2 different dataLayer namespaces.  Most of people just push the info to window.dataLayer variable, but it could be window.myOwnDataLayerNS . So we’d need to adapt our dataLayer pushes to that variable name.

    So we’re going to setup a Macro that is going to take care of returning the right dataLayer namespace name for our current container. This way we’ll not messing around with other’s containers namespaces and our scripts will work in any container even if we’re using a custom dataLayer name.

    First, we’ll need to enable the “Container IDbuilt-in Variable within our container:

    Now we’re adding a new variable, that will take care of reading the current “Container ID” and looking for it’s right dataLayer namespace. ( Remember, you’ll be able to find this macro code at the bottom of this post )

    So, we should have now a new variable name {{getDataLayerNameSpace}} available in Google Tag Manager, that would be allow us to send the pushes to the right dataLayer variable for our current container, without the need to taking care of the variable name defined on the gtm snippet.

    Then, we’ll be able to send the pushes to the dataLayer this way:

        window[{{getDataLayerNameSpace}}].push(
                                 {'event':'someEventName',
                                  'eventValue','Some Event Value'
                                 });
    

    Did you have found this post useful ?, then go ahead and leave us a comment πŸ™‚

     Macro Source Code

    function() 
    {    
        var gtm_objects = [];    
        var gtm_counter = 0;    
        for (var i in window.google_tag_manager) 
        {
            if (typeof(google_tag_manager[i]) == "object" && i.match(/^GTM-/)) {
                gtm_objects.push({'accountId': i});
            }
            if (typeof(google_tag_manager[i]) == "object" && !i.match(/^GTM-/)) {
                gtm_objects[gtm_counter].dataLayer = i;
                gtm_counter++;
            }
        }
        for (var i in gtm_objects) 
           if (gtm_objects[i].accountId == "{{Container ID}}")
                return gtm_objects[i].dataLayer;
    }
    
  • Blocking your tags firing from your own IPs with Google Tag Manager

    The subject about how to exclude your own visits from GA has been commented before loads of times, most of time filters are used to keep out our own visits from Google Analytics , but some other tools may not have the possibility to filter out our own visits. This is why we’re going to use Google Tag Manager to block all our tracking pixels, conversions pixels, custom tags, or whatever tag type we want.

    We’re going to lay on a Lookup Table type macro and the dataLayer to get this sorted out πŸ™‚ . Firstly, as JavaScript is not able to get the user’s IP address, we’re going to push the current user’s ip address to the dataLayer. We could use some JS based services to get the user’s IP but for using it this way we’ll need to delay our tags ( as we need to wait to receive the data from some external service, which could be down, or not reachable from user’s connection. So we’ll need to pass the request to our developers or IT team to implement a new key into the default dataLayer.

    As our site is certainly using some kind of server-side language, this should be a problem at all πŸ™‚ . Let’s see an example about how could we do this using PHP :

    dl_optout

    Any other language will be able to do this

    Ruby

    request.env["REMOTE_ADDR"]

    ASP

    Request.ServerVariables("REMOTE_ADDR")

    PHP

    $_SERVER["REMOTE_ADDR"]

    Python

    os.environ['REMOTE_ADDR']

    As you see all languages give you access to the “REMOTE_ADDR” header variable, but this will fail if the user’s behind a proxy. So usually we’ll want to check for “HTTP_X_FORWARDED_FOR” and it’s not present we’ll use “REMOTE_ADDR“, this is up to the developers to print the visitor IP Address is the best way they can.

    Now, let’s setup 2 new macros, one of them will take care of reading our user_ip value from the dataLayer, the other will be a lookup table macro that will will use this first macro for the condition:

    Macro that grabs the visitor ip address from the dataLayer
    Macro that grabs the visitor ip address from the dataLayer
    Lookup Table that makes the magic happen
    Lookup Table that makes the magic happen

    Now we have 2 ways to go, either using a blocking rule to our tags (we’ll be using this if we want to block certain tags as we should now Blocking Rules prevail over Firing Rules ) , adding a new condition to our existing rules (if we want to block all tags depending on a generic rule, like “All Pages” :

    dl_optout_rule_all
    dl_optout_rule_notall

    There we go, now we could easily avoid sending data to Google Analytics from our Office IPs , but for any other tag that we’re using withn Google Tag Manager.

  • Tracking the browser orientation status and changes

    Last day we were talking about how to measure if our site was showing a responsive layout to our users, and today we’re going to expand that tracking post with orientation tracking for our sites.

    We could use the Media Queries to match the orientation, but as this is not much standard we’re going to use the window.orientation value and the viewPort Width and Height values to guess the current page orientation and pass that info to Google Analytics and track it using a custom dimension.

    orientation-tracking

    Actually those API’s and features are not support for much of the current browsers, so we’re going to write some extra code to rely on the viewPort width/height values if orientation values and event are not available within the current user’s browser.

    http://caniuse.com/#search=orientation
    http://caniuse.com/#search=orientation

    as

    <script>// <![CDATA[
    function getPageOrientation() { var orientation = window.orientation || window.screen.orientation; if(orientation) { // If the orientation is 0 or 180 page is in portrait mode, else landscape return (orientation == 0 || orientation == 180) ? "portrait" : "landscape"; }else{ // Let's rely on the viewPort values instead if orientation is no supporte by the browser var vpWidth= (Math.max(document.documentElement.clientWidth, window.innerWidth || 0)); var vpHeigth = (Math.max(document.documentElement.clientHeight, window.innerHeight || 0)); return vpHeigth>vpWidth ? "portrait" : "landscape";
      }
    }
    ga('set','dimensionYY',getPageOrientation());
    // ]]></script>

    As was mentioned before window.screen.orientation is a Experimental feature, therefore we’re going trying to use the orientationchange event if it’s available and if not we’ll be using to use the “resize” event that is widely supported by most browsers:

    <script>
    // Listen for orientation changes 
    // If present use orientation value and native event 
    var orientation = window.orientation || window.screen.orientation;  
    if(orientation)   
         window.addEventListener("orientationchange", function() {
             ga('send', 'Orientation Change', getPageOrientation(), null, null, true);
         }, false); 
    // If not, let's use resize event
    } else {
         window.addEventListener("resize", function() {
             ga('send', 'Orientation Change', getPageOrientation(), null, null, true);
         }, false);
     }
    </script>

    And this is how you could get some insights of what are most common orientations being used by your site visitors.

    Thanks fly to @SimoAhava for pointing me out to some code mess up I did while copying the codes into the post.

  • Tracking a responsive site and the media queries changes

    Every day more sites choose to use a responsive designs for their new sites, making it somekind difficult to track what version of our page is being showed to the user (as all we may now, browser detection is not perfect at all, most of them are based on the User Agent that can be easily faked).

    This post will try to show you the different ways to track if user’s are viewing a responsive layout or not, and using the Google Analytics Event Tracking to even track the layout changes if the browser window is resized by the user.

    New browsers allow you to query the current @media actually being used on the page using the matchMedia API though not all browsers have access to this API. The responsive designs work setting some new css’s rules based on the current page viewPort of the browser, this is; the usable width and height within the current browser window.

    As each site may have their own defined width limits to switch to a new layout, we’ll need to check out CSS files to find which one we’ll be using to detect if the user is viewing a different responsive layout but the desktop one.

    media_responsive

    It may happen that our site has more than one @media’s defined to make the site fully compatible with some specific devices ( ipad1, ipad2, etc ). If this is our case I recomend just to check for the main @media that really makes the difference, or if you want to go further you could even improve the current post examples to track every single available layour within your site.

    var matchMedia = window.matchMedia || window.msMatchMedia; var customVarResponsive; if(window.matchMedia) window.matchMedia("(max-width: 992px)").matches ? customVarResponsive='yes' : customVarResponsive='no'; else customVarResponsive = 'no'; ga('set','dimensionXX',customVarResponsive);

    As we’ve mentioned before not all browsers does support the mediaMatch API (You can check browsers support in the table below), so there’s some other more standard method to get this info, and instead of matching the current used @media, we’ll need to check the current viewPort width size againts the max-width defined in our CSS file:

    Source: http://caniuse.com/#feat=matchmedia
    Source: http://caniuse.com/#feat=matchmedia

    The following snippet will let you track the current browser’s viewPort and it’s fully compatible with all the browsers:

    if((Math.max(document.documentElement.clientWidth, window.innerWidth || 0))>=992)
      var customVarResponsive = "no";
    else
      var customVarResponsive = "yes";
    ga('set','dimensionXX',customVarResponsive);

    In any case using the matchMedia API is fancier way to achieve this, and is widely supported plus it give us more flexibility for example allowing us to use a listener to monitor the @media changes without needing to query the current viewPort size every X seconds, and why not sending an event if the user’s layout changed after the initial page load πŸ™‚

    <script>
    // IE msMatchMedia var matchMedia = window.matchMedia || window.msMatchMedia; function getMaxWidth (mq) { if(mq.matches) // We do not want to send an interactive event ga('send','Responsive Tracking','Went Responsive',null.null,true); else ga('send','Responsive Tracking','Went Desktop',null.null,true); } if(matchMedia) responsiveCheck = window.matchMedia("(max-width: 992px)"); responsiveCheck.addListener(getMaxWidth);
    </script>
    
    
    
    
    

    There we have it, everytime the current user @media’s changes, a new event will be send to Google Analytics using the Event Tracking, or this can be extended to be user with any other tracking tool with not much efforts but calling our analytics tool method to do the event tracking, or setting the custom dimensions.

    Of couse this would allow us to have a different tracking for our sites, as we could define different event’s for each of our layouts.

  • Tracking fragments based navigation websites with GTM the right way

    We’re going to learn how to track those websites where the navigation is based on Ajax, ie: no pages reloads with Google Tag Manager.  As you may know a full page reload is needed in order to have Google Analytics tracking your pageviews, but those sites like the ones based on AngularJS are not reloading the page just the current page content, so this new pages won’t be tracked by our Analytics tools, or even catched by our conversion pixels.

    Those sites use the popstate event, to push a live URI change in our browser without reloading the page. We could write our own “popstate listener” to catch up those changes, but GTM is pretty smart and it has a tool called history listener , that we’ll use to track those uri changes.

    Every time that GTM detects a popstate event it will push an automatic event to the dataLayer called “gtm.historyChange” along with the previous fragment value and the new pushed one :

    event“gtm.historyChange”
    gtm.historyChangeSource
     “popstate”
    gtm.newHistoryState
     null
    gtm.newUrlFragment
     “contact”
    gtm.oldHistoryState
     null
    gtm.oldUrlFragment
     “”

    So basically what we’re going to do is adding a new pageview tag that is gonna be fired when this event is pushed to the dataLayer, but there’re some points that we need to have in mind:

    1. What happens if an user lands directly into a page with a hashtag, Google Analytics will just grab the current location.pathname + location.search
    2. Since Universal Analytics was launched, the referral behaviour changed, now it will overwrite the current visit attribution when a new referral is found while this domain names has not been added to referral exclusion list within our properties configuration section.

    Luckily we can think in some extra configurations in our Google Tag Manager container configuration to fix those previously mentioned situations.

    AngularJS

    Needed

    • 3 tags ( 1 will be fired when page is fully loaded, the second one will be fired based on the “gtm.historyChange” event and the last one will be the history listener tag type )
    • 2 firing rules ( First rule will allow us to fire a pageview web the user lands in our website the other will allow us to fire a virtual pageview when the uri changes without a page load )
    • 4 macros ( Here’s were we’re going to deal with the 2 problems we mentioned about before )

    Tags

    History Listener Tag

     Page Load Pageview Recording Tag

    Fragment Change Pageview Recording Tag

    Rules

    Rule for the default pageView Tag

    Rule for the fragment change pageView Tag

    Macros

    Macro for fixing the pagePath for page load Pageviews

    Macro for fixing the pagePath for Fragment Pageviews

    Null Value Returning Macro

    There we go, now our Google Tag Manager container will record the right pageviews taking in mind the fragment part when registering the pagePath, and it will take care of firing a new virtual pageView when the pages fragment is uptated. Just off the record, this will work too for implementation not only changing the fragment but for all page types that change the uri path in the fly. ( ie: /contact.php to /aboutus.php without a page reload )

    Any thoughs about this post ?, any improvement that you’ll have done to have a better tracking ?, Just leave a comment so we can discuss about it πŸ™‚

  • Google using ISPs to cache Google Analytics endpoint

    I don’t really know if this is happening users using different ISP’s but starting from today I’ve noticed that all my requests to www.google-analytics.com were being served from a not usual but familiar IP address range, and the response time was just 9ms. Hey just a great improvement from the 42ms of average I’m usually getting from Google Analytics servers.

    thyngster@hq:~$ ping www.google-analytics.com
    PING www-google-analytics.l.google.com (212.142.160.238) 56(84) bytes of data.
    64 bytes from cache.google.com (212.142.160.238): icmp_seq=1 ttl=59 time=8.80 ms
    64 bytes from cache.google.com (212.142.160.238): icmp_seq=2 ttl=59 time=9.12 ms
    64 bytes from cache.google.com (212.142.160.238): icmp_seq=3 ttl=59 time=8.66 ms
    64 bytes from cache.google.com (212.142.160.238): icmp_seq=4 ttl=59 time=8.19 ms
    
    --- www-google-analytics.l.google.com ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3004ms
    rtt min/avg/max/mdev = 8.190/8.698/9.129/0.344 ms

    That’s is, Google Analytics files are hits are being server locally from my ISP. It doesn’t seems to be a problem with my ISP, since it’s the Google Nameservers who are delegating that dns response:

    thyngster@hq:~$ dig +trace www.google-analytics.com
    
    ; <<>> DiG 9.9.5-3-Ubuntu <<>> +trace www.google-analytics.com
    ;; global options: +cmd
    .                       443274  IN      NS      b.root-servers.net.
    .                       443274  IN      NS      j.root-servers.net.
    .                       443274  IN      NS      e.root-servers.net.
    .                       443274  IN      NS      a.root-servers.net.
    .                       443274  IN      NS      m.root-servers.net.
    .                       443274  IN      NS      h.root-servers.net.
    .                       443274  IN      NS      i.root-servers.net.
    .                       443274  IN      NS      c.root-servers.net.
    .                       443274  IN      NS      d.root-servers.net.
    .                       443274  IN      NS      k.root-servers.net.
    .                       443274  IN      NS      l.root-servers.net.
    .                       443274  IN      NS      g.root-servers.net.
    .                       443274  IN      NS      f.root-servers.net.
    ;; Received 531 bytes from 212.142.144.66#53(212.142.144.66) in 295 ms
    
    com.                    172800  IN      NS      m.gtld-servers.net.
    com.                    172800  IN      NS      l.gtld-servers.net.
    com.                    172800  IN      NS      k.gtld-servers.net.
    com.                    172800  IN      NS      j.gtld-servers.net.
    com.                    172800  IN      NS      i.gtld-servers.net.
    com.                    172800  IN      NS      h.gtld-servers.net.
    com.                    172800  IN      NS      g.gtld-servers.net.
    com.                    172800  IN      NS      f.gtld-servers.net.
    com.                    172800  IN      NS      e.gtld-servers.net.
    com.                    172800  IN      NS      d.gtld-servers.net.
    com.                    172800  IN      NS      c.gtld-servers.net.
    com.                    172800  IN      NS      b.gtld-servers.net.
    com.                    172800  IN      NS      a.gtld-servers.net.
    com.                    86400   IN      DS      30909 8 2 E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CF C41A5766
    com.                    86400   IN      RRSIG   DS 8 1 86400 20141130170000 20141123160000 22603 . KcH1dZuKA0dS9oDK2Wy8Iq/axbkR1/Dd0OnU3zgAUI88ym4rezyeHIJQ z6f7T2Ym2Ese+4ma1n0/9Q8iMvvTw8LAv+0ICzCuBtGQuCY3LhJ8uaRG AHoCVwv772zkalcRQuq07cxfllZmNykMyUgPcp/ViyQZtWcB/3eGFwJj mII=
    ;; Received 748 bytes from 198.41.0.4#53(a.root-servers.net) in 915 ms
    
    google-analytics.com.   172800  IN      NS      ns2.google.com.
    google-analytics.com.   172800  IN      NS      ns1.google.com.
    google-analytics.com.   172800  IN      NS      ns3.google.com.
    google-analytics.com.   172800  IN      NS      ns4.google.com.
    CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN NSEC3 1 1 0 - CK0QFMDQRCSRU0651QLVA1JQB21IF7UR NS SOA RRSIG DNSKEY NSEC3PARAM
    CK0POJMG874LJREF7EFN8430QVIT8BSM.com. 86400 IN RRSIG NSEC3 8 2 86400 20141129055344 20141122044344 48758 com. mKMCjqttB9SBFDa9+UYTil3/baYuVXdwSsrBjWybi05WCkGUvVBe14yF HrrQUMAue5E8qBwbESuqqhwgEdspaybxel8gCwFHFCAAl12Ok5VGtMCK gYlu3/nXwfFtN46WYMPU3k83n1j/UhSypjE6OajPWV/nKVzcKfHwYQ39 NM4=
    PMC1T49FLPALQ6HR7VPE0GQRFGSM38PS.com. 86400 IN NSEC3 1 1 0 - PMCAVG0VD8T2G2NPG5K6HV9F6T4VRMVT NS DS RRSIG
    PMC1T49FLPALQ6HR7VPE0GQRFGSM38PS.com. 86400 IN RRSIG NSEC3 8 2 86400 20141128052656 20141121041656 48758 com. cPUz3qVmzylXFxkLa870sFXMxAjAodtPcK3Nkoric83FKdaegulpxd9c eoY5zs3q5fKDNz8MjSkp4GO6MXsrbU2zOi9mueWmhVbb5OPAW7Od0DNg C6g+fSCRbIaOzsFHStWry9i7Rj9ZKPW8tayKQWuP2+p7FyTfATeg5IBA Jto=
    ;; Received 681 bytes from 192.41.162.30#53(l.gtld-servers.net) in 297 ms
    
    www.google-analytics.com. 86400 IN      CNAME   www-google-analytics.l.google.com.
    www-google-analytics.l.google.com. 300 IN A     212.142.160.238
    www-google-analytics.l.google.com. 300 IN A     212.142.160.223
    www-google-analytics.l.google.com. 300 IN A     212.142.160.240
    www-google-analytics.l.google.com. 300 IN A     212.142.160.229
    www-google-analytics.l.google.com. 300 IN A     212.142.160.234
    www-google-analytics.l.google.com. 300 IN A     212.142.160.230
    www-google-analytics.l.google.com. 300 IN A     212.142.160.251
    www-google-analytics.l.google.com. 300 IN A     212.142.160.212
    www-google-analytics.l.google.com. 300 IN A     212.142.160.218
    www-google-analytics.l.google.com. 300 IN A     212.142.160.216
    www-google-analytics.l.google.com. 300 IN A     212.142.160.208
    www-google-analytics.l.google.com. 300 IN A     212.142.160.249
    www-google-analytics.l.google.com. 300 IN A     212.142.160.241
    www-google-analytics.l.google.com. 300 IN A     212.142.160.227
    www-google-analytics.l.google.com. 300 IN A     212.142.160.245
    www-google-analytics.l.google.com. 300 IN A     212.142.160.219
    ;; Received 342 bytes from 216.239.32.10#53(ns1.google.com) in 42 ms
    

    So it seems that’s not really my isp who’s acting by it’s own serving me the ga.js/analytics.js and receiving my hits to the Google Analytics endpoint in some of their caching servers.

    ga_caching

    Actually even if a 304 Not Modified response code is being returned, hits are being registered in Google Analytics, so that shouldn’t be something about I should care about at the moment. I must add that ssl.google-analytics.com is not being cached for me at the moment:

    thyngster@hq:~$ host ssl.google-analytics.com
    ssl.google-analytics.com is an alias for ssl-google-analytics.l.google.com.
    ssl-google-analytics.l.google.com has address 74.125.230.62
    ssl-google-analytics.l.google.com has IPv6 address 2a00:1450:4003:807::101e
    

    I must say that this looks like a good thing for our Google Analytics implementations as hits will be sent faster by our user’s browsers, but in any case if you have any server-side implemention on Google Analytics using the Measurement Protocol, I’d add the “z” parameter to avoid any kind of malfunction on those cache servers preventing the hits being sent to real Google Analytics endpoint , even if that parameter is not obligatory ( it just needs to be a randomly generated number ).

    As I said I noticed this today, I’m not sure since when it has been up, or if Google is doing the same with any other ISPs . I think that they have some agreements with some isp’s to cachΓ© YouTube content too, saving bandwidth to Google and the ISP’s and giving to users a better experience while viewing videos.

    I tried with different browsers, different computers, different Operating systems and I’m experiencing the same behaviour for all of them, so I’m discarding some kind of configuration by me.