Google Analytics script injection

Problem this snippet solves:

Add google analytics script in the html content of the HTTP response. Works also for other Analytics providers like Piwik.

How to use this snippet:

Installation

Files

The code below has to be imported as an ifile.

By default, you must name this ifile google.js but you can change it in the irule if required.

Google Analytics code :

<!-- Google Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '$static::tracking_id', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->

Piwik javascript code :

<!-- Piwik -->
<script type="text/javascript">
  var _paq = _paq || [];
  _paq.push(['trackPageView']);
  _paq.push(['enableLinkTracking']);
  (function() {
    var u="//$static::piwik_url/";
    _paq.push(['setTrackerUrl', u+'piwik.php']);
    _paq.push(['setSiteId', {$static::siteid}]);
    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
    g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
  })();
</script>
<!-- End Piwik Code -->

irule

You need to install the irule on your Virtual Server.

Variables

set static::tracking_id "UA-XXXXX-Y"                            # replace the Google Tracking ID by your own
set static::siteid "UA-XXXXX-Y"                                 # replace the Piwik Site ID by your own
set static::piwik_url "https://www.mypiwik.com/piwik/piwik"     # replace the Piwik URL by your own

Features

Version 1.0

  • Insert Google Analytics JS code within html response
  • support for Piwik JS insertion
  • Manage Multiple TrackingID by hostname (see Multiple "hostname and TrackingID section")

Backlog

  • Add logging

External links

Github : https://github.com/e-XpertSolutions/f5

BONUS : Multiple hostname and TrackingID

Prerequisite

You need to add a string based Datagroup named HOST_TRACKING_MAPPING.

ltm data-group internal HOST_TRACKING_MAPPING {
    records {
        blog.e-xpertsolutions.com {
            data UA-XXXXX-Z
        }
        www.e-xpertsolutions.com {
            data UA-XXXXX-Y
        }
    }
    type string
}

The google.js ifile need to be replaced by the following example :

<!-- Google Analytics -->
<script>
    window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
    ga('create', '$tracking_id', 'auto');
    ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->

Irule

when RULE_INIT {
    set static::default_trackingid "UA-XXXXX-Y"
}

when HTTP_REQUEST {
    HTTP::header remove "Accept-Encoding"
    set host [HTTP::host]
}

when HTTP_RESPONSE {
    if { [HTTP::header Content-Type] contains "text/html" } {
        if { [HTTP::header exists "Content-Length"] } {
            set content_length [HTTP::header "Content-Length"]
        } else {
            set content_length 1000000
        }
        if { $content_length > 0 } {
            HTTP::collect $content_length
        }
    }
}
when HTTP_RESPONSE_DATA { 
    set search "</head>"
    set tracking_id [class match -value -- $host equals HOST_TRACKING_MAPPING]
    if { $tracking_id eq "" } {
        set tracking_id $static::default_trackingid
    }
    HTTP::payload replace 0 $content_length [string map [list $search "[subst -nocommands -nobackslashes [ifile get google.js]]</head>"] [HTTP::payload]]
    HTTP::release
}

Code :

when RULE_INIT {
set static::tracking_id "UA-XXXXX-Y"
set static::siteid "XXXXX"
set static::piwik_url "https://www.piwik.url/piwik/piwik"
}

when HTTP_REQUEST {
HTTP::header remove "Accept-Encoding"
}

when HTTP_RESPONSE {
if { [HTTP::header Content-Type] contains "text/html" } {
if { [HTTP::header exists "Content-Length"] } {
set content_length [HTTP::header "Content-Length"]
} else {
set content_length 1000000
}
if { $content_length > 0 } {
HTTP::collect $content_length
}
}
}
when HTTP_RESPONSE_DATA { 
    set search ""
    HTTP::payload replace 0 $content_length [string map [list $search "[subst -nocommands -nobackslashes [ifile get google.js]]"] [HTTP::payload]]
    HTTP::release
}

Tested this on version:

11.5
Updated Jun 06, 2023
Version 2.0
  • We did everything as described trying to get some data to Google Analytics, single hostname, following the instructions. No traffic reaches Google Analytics. Do you guys have possible update on this topic?

     

    We use BIG-IP LTM.

     

    Thanks!

     

  • We arrived at a solution using HTML profiles - am I overlooking any major downsides to this approach?

    HTML profile (abbreviated):

    ltm profile html /Common/html-ga {
        app-service none
        content-detection disabled
        content-selection { application/xhtml+xml text/xhtml text/html }
        defaults-from /Common/html
        description none
        rules {
            /Common/GT-Private
            /Common/GA-Private
        }
    }
    ltm html-rule tag-append-html /Common/GT-Private {
        action {
            text ""
        }
        description "Google Tag Manager"
        match {
            tag-name body
        }
    }
    ltm html-rule tag-prepend-html /Common/GA-Private {
        action {
            text ""
        }
        description "Google Analytics"
        match {
            tag-name /head
        }
    }
    

    Sample iRule (optional and abbreviated):

    when HTTP_REQUEST {
        set disable_html 0
    
        switch -glob [string tolower [HTTP::uri]] {
            /secure_app* {
                 special case for app that leaks data in URLs
                pool secure_app
                set disable_html 1
            } /legacy_app/pagenotfound/* {
                 ... special case for app that redirects 404s ...
                pool legacy_app
                set disable_html 1
            } default {
                 ... normal logic ...
                pool main_app
            }
        }
    }
    when HTTP_RESPONSE {
        if { [HTTP::status] == 404 || [HTTP::status] == 403 || $disable_html == 1 }{
            HTML::disable
        }
    }
    
  • I was able to get this to work for a different .js file insertion but had to make a change to the string map line at the end to remove a set of quotes and include the partition.

    HTTP::payload replace 0 $content_length [string map [list $search [subst -nocommands -nobackslashes [ifile get "/Common/newrelic.js"]]</head>] [HTTP::payload]]