Featured image of post Blog Analytics

Blog Analytics

Discover how to add blog analytics with Azure Application Insights, gaining insights into user activity.

It’s still early days, but I’m pretty happy with my blog so far. The hosting solution is working well and I really like the look of the Stack theme. One thing that is missing for me at the moment is analytics. Admittedly, there’s not much content right now and there’s probably no one looking, but I’m the curious type, so it would be nice to see if any of my posts do attract any visitors.

Which tool?

There are tons of different solutions available for blog analytics, but sticking with the theme of trying to use Azure services to fulfil the requirement, I looked to Application Insights.

App Insights’ pricing model for data ingestion offers 2 plans (Basic Logs and Analytic Logs). The default being Analytic Logs, which has quite a generous allowance of 5GB/month being free to ingest. Basic logs have a reduced ingestion cost, are more restricted on querying and aren’t retained for as long.

Configuring

App Insights can be enabled through the Static Web App, but unfortunately it requires that your app has an API. As that’s not a requirement for my blog I need to find an alternative way of capturing the data.

Static Web App Application Insights

App Insights also offers a JavaScript SDK that you can drop into your app, on each page that you want to collect telemetry. Microsoft provide the following piece of code to enable App Insights in your app:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script type="text/javascript">
!function(v,y,T){var S=v.location,k="script",D="instrumentationKey",C="ingestionendpoint",I="disableExceptionTracking",E="ai.device.",b="toLowerCase",w=(D[b](),"crossOrigin"),N="POST",e="appInsightsSDK",t=T.name||"appInsights",n=((T.name||v[e])&&(v[e]=t),v[t]||function(l){var u=!1,d=!1,g={initialize:!0,queue:[],sv:"6",version:2,config:l};function m(e,t){var n={},a="Browser";return n[E+"id"]=a[b](),n[E+"type"]=a,n["ai.operation.name"]=S&&S.pathname||"_unknown_",n["ai.internal.sdkVersion"]="javascript:snippet_"+(g.sv||g.version),{time:(a=new Date).getUTCFullYear()+"-"+i(1+a.getUTCMonth())+"-"+i(a.getUTCDate())+"T"+i(a.getUTCHours())+":"+i(a.getUTCMinutes())+":"+i(a.getUTCSeconds())+"."+(a.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z",iKey:e,name:"Microsoft.ApplicationInsights."+e.replace(/-/g,"")+"."+t,sampleRate:100,tags:n,data:{baseData:{ver:2}}};function i(e){e=""+e;return 1===e.length?"0"+e:e}}var e,n,f=l.url||T.src;function a(e){var t,n,a,i,o,s,r,c,p;u=!0,g.queue=[],d||(d=!0,i=f,r=(c=function(){var e,t={},n=l.connectionString;if(n)for(var a=n.split(";"),i=0;i<a.length;i++){var o=a[i].split("=");2===o.length&&(t[o[0][b]()]=o[1])}return t[C]||(t[C]="https://"+((e=(n=t.endpointsuffix)?t.location:null)?e+".":"")+"dc."+(n||"services.visualstudio.com")),t}()).instrumentationkey||l[D]||"",c=(c=c[C])?c+"/v2/track":l.endpointUrl,(p=[]).push((t="SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details)",n=i,o=c,(s=(a=m(r,"Exception")).data).baseType="ExceptionData",s.baseData.exceptions=[{typeName:"SDKLoadFailed",message:t.replace(/\./g,"-"),hasFullStack:!1,stack:t+"\nSnippet failed to load ["+n+"] -- Telemetry is disabled\nHelp Link: https://go.microsoft.com/fwlink/?linkid=2128109\nHost: "+(S&&S.pathname||"_unknown_")+"\nEndpoint: "+o,parsedStack:[]}],a)),p.push((s=i,t=c,(o=(n=m(r,"Message")).data).baseType="MessageData",(a=o.baseData).message='AI (Internal): 99 message:"'+("SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details) ("+s+")").replace(/\"/g,"")+'"',a.properties={endpoint:t},n)),i=p,r=c,JSON&&((o=v.fetch)&&!T.useXhr?o(r,{method:N,body:JSON.stringify(i),mode:"cors"}):XMLHttpRequest&&((s=new XMLHttpRequest).open(N,r),s.setRequestHeader("Content-type","application/json"),s.send(JSON.stringify(i)))))}function i(e,t){d||setTimeout(function(){!t&&g.core||a()},500)}f&&((n=y.createElement(k)).src=f,!(o=T[w])&&""!==o||"undefined"==n[w]||(n[w]=o),n.onload=i,n.onerror=a,n.onreadystatechange=function(e,t){"loaded"!==n.readyState&&"complete"!==n.readyState||i(0,t)},e=n,T.ld<0?y.getElementsByTagName("head")[0].appendChild(e):setTimeout(function(){y.getElementsByTagName(k)[0].parentNode.appendChild(e)},T.ld||0));try{g.cookie=y.cookie}catch(h){}function t(e){for(;e.length;)!function(t){g[t]=function(){var e=arguments;u||g.queue.push(function(){g[t].apply(g,e)})}}(e.pop())}var s,r,o="track",c="TrackPage",p="TrackEvent",o=(t([o+"Event",o+"PageView",o+"Exception",o+"Trace",o+"DependencyData",o+"Metric",o+"PageViewPerformance","start"+c,"stop"+c,"start"+p,"stop"+p,"addTelemetryInitializer","setAuthenticatedUserContext","clearAuthenticatedUserContext","flush"]),g.SeverityLevel={Verbose:0,Information:1,Warning:2,Error:3,Critical:4},(l.extensionConfig||{}).ApplicationInsightsAnalytics||{});return!0!==l[I]&&!0!==o[I]&&(t(["_"+(s="onerror")]),r=v[s],v[s]=function(e,t,n,a,i){var o=r&&r(e,t,n,a,i);return!0!==o&&g["_"+s]({message:e,url:t,lineNumber:n,columnNumber:a,error:i,evt:v.event}),o},l.autoExceptionInstrumented=!0),g}(T.cfg));function a(){T.onInit&&T.onInit(n)}(v[t]=n).queue&&0===n.queue.length?(n.queue.push(a),n.trackPageView({})):a()}(window,document,{
src: "https://js.monitor.azure.com/scripts/b/ai.2.min.js",
// name: "appInsights",
// ld: 0,
// useXhr: 1,
crossOrigin: "anonymous",
// onInit: null,
cfg: { // Application Insights Configuration
 connectionString: "YOUR_CONNECTION_STRING"
}});
</script>

The instructions for adding App Insights seem simple enough. The problem was that I’d had no experience with Hugo (the platform this blog is built on) until a few weeks ago and so I didn’t know how to get this JavaScript into the app. Then I found a very helpful blog post by Peter De Tender that describes just how to do this in great detail.

After following the instructions in Peter’s blog I noticed a slight difference in the configurations. His post is using the App Insights instrumentationKey but the MS documentation is using the connectionString. I then found a notice in the doc that says:

On March 31, 2025, support for instrumentation key ingestion will end. Instrumentation key ingestion will continue to work, but we’ll no longer provide updates or support for the feature. Transition to connection strings to take advantage of new capabilities.

I attempted to switch to using the connection string but I noticed that requests were being blocked by one of my Chrome extensions. After a short investigation I found that the telemetry is sent to different endpoints depending on the config:

Config Telemtry Endpoint
Instrumentation key https://dc.services.visualstudio.com/v2/track
Connection string https://{region}.in.applicationinsights.azure.com//v2/track

My Chrome extension was blocking requests to the azure.com domain. As there didn’t seem to be any obvious way around this and I’ve got just under 2 years to work it out, I’ll stick with the instrumentation key for now.

Once the telemetry was working again, I made one more addition to the appinsights.html to check the URL of the site. If it’s running on localhost then don’t add the App Insights JavaScript SDK to the page.

1
2
{{ if (and (.Site.Params.azureAppInsightsKey)
   (ne (printf "%v" $.Site.BaseURL) "http://localhost:1313/")) }}

This little check will prevent loads of false data being sent to App Insights from my local server.

Show me the data

Let’s go and take a look at App Insights and make sure the data has been making it through. There’s a short delay of a few minutes before the data is available in App Insights, but once it’s there you can find it under the Usage section.

Clicking on the Users item in the menu we’re first presented with a chart showing the number of unique visitors over the past 24 hours.

App Insights Users chart

Scrolling down the page there’s a button labelled View More Insights. Clicking this will show more detail about the visitors to the site. We get to see the country they’re from, as well as the OS and browser versions on their devices.

App Insights Detailed

A little bit further down is a section showing the timelines of particular users. Clicking one of these boxes will give us a very detailed view of a user’s journey through the site. It shows how long a user spent on the site, the time they visited as well as the pages they visited in order.

App Insights User Journey

Clicking on the More item in the menu presents a gallery of template workbooks that show the data in a variety of different ways.

App Insights Gallery

One template that was of particular interest to me was Analysis of Page Views. This view will be incredibly useful in enabling me to understand which posts are most popular and attracting the most visitors.

Page Views

Conclusion

As we’ve seen, App Insights can be quite a powerful tool for viewing users’ activity on a site, and for small use cases that don’t generate too much data, it can even be free.