Tracking API usage with Google Analytics

Edit on GitHub

So you have an API. Congratulations! You should have one. But how do you track who uses it, what client software they use and so on? You may be logging API calls yourself. You may be relying on services like Apigee.com who make you pay (for a great service, though!). Being cheap, we thought about another approach for MyGet. We’re already using Google Analytics to track pageviews and so on, why not use Google Analytics for tracking API calls as well?

Meet GoogleAnalyticsTracker. It is a three-classes assembly which allows you to track requests from within C# to Google Analytics.

Go and  fork this thing and add out-of-the-box support for WCF Web API, Nancy or even “plain old” WCF or ASMX!

Using GoogleAnalyticsTracker

Using GoogleAnalyticsTracker in your projects is simple. Simply Install-Package GoogleAnalyticsTracker and be an API tracking bad-ass! There are two things required: a Google Analytics tracking ID (something in the form of UA-XXXXXXX-X) and the domain you wish to track, preferably the same domain as the one registered with Google Analytics.

After installing GoogleAnalyticsTracker into your project, you currently have two options to track your API calls: use the Tracker class or use the included ASP.NET MVC Action Filter.

Here’s a quick demo of using the Tracker class:

1 Tracker tracker = new Tracker("UA-XXXXXX-XX", "www.example.org"); 2 tracker.TrackPageView("My API - Create", "api/create");

Unfortunately, this class has no notion of a web request. This means that if you want to track user agents and user languages, you’ll have to add some more code:

1 Tracker tracker = new Tracker("UA-XXXXXX-XX", "www.example.org"); 2 3 var request = HttpContext.Request; 4 tracker.Hostname = request.UserHostName; 5 tracker.UserAgent = request.UserAgent; 6 tracker.Language = request.UserLanguages != null ? string.Join(";", request.UserLanguages) : ""; 7 8 tracker.TrackPageView("My API - Create", "api/create");

Whaah! No worries though: there’s an extension method which does just that:

1 Tracker tracker = new Tracker("UA-XXXXXX-XX", "www.example.org"); 2 tracker.TrackPageView(HttpContext, "My API - Create", "api/create");

The sad part is: this code quickly clutters all your action methods. No worries! There’s an ActionFilter for that!

1 [ActionTracking("UA-XXXXXX-XX", "www.example.org")] 2 public class ApiController 3 : Controller 4 { 5 public JsonResult Create() 6 { 7 return Json(true); 8 } 9 }

And what’s better: you can register it globally and optionally filter it to only track specific controllers and actions!

1 public class MvcApplication : System.Web.HttpApplication 2 { 3 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 4 { 5 filters.Add(new HandleErrorAttribute()); 6 filters.Add(new ActionTrackingAttribute( 7 "UA-XXXXXX-XX", "www.example.org", 8 action => action.ControllerDescriptor.ControllerName == "Api") 9 ); 10 } 11 }

And here’s what it could look like (we’re only tracking for the second day now…):

WCF Web API analytics google

We even have stats about the versions of the NuGet Command Line used to access our API!

NuGet API tracking Google

Enjoy! And fork this thing and add out-of-the-box support for WCF Web API, Nancy or even “plain old” WCF or ASMX!

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

37 responses

  1. Avatar for Radenko
    Radenko January 21st, 2012

    Thanks for nice post.
    I need version that works with WCF Web api.
    Probably it is easy to implement same thing in WCF Web Api ?

  2. Avatar for Mark Gibaud
    Mark Gibaud February 6th, 2012

    Maarten,

    That looks great - do you have any advice on how you could segment/filter API usage on API Keys?

    Maybe (mis)use the UserAgent or something else that only makes sense when GA is interacting with a browser?

    Or perhaps a GA Custom Variable?

  3. Avatar for maartenba
    maartenba February 17th, 2012

    The latest version now also supports custom variables :-)

  4. Avatar for Andy
    Andy February 26th, 2012

    Thanks Maarten! I have been struggling with trying to get done myself, this is EXACTLY what I am after. The GA platform has really turned into something much more than the stats programs of yesteryear, I use this stuff everyday.

    Regards

  5. Avatar for Markive
    Markive October 25th, 2013

    Thanks Maarten! This works great.. Is there a way to get my Web Api to automatically use the ActionFilter without having to decorate every function with it? For example overriding APIController.. My web applications is Web Forms not MVC, so I can't use the RegisterGlobalFilters() function..

  6. Avatar for Kristoffer Andersson
    Kristoffer Andersson February 17th, 2014

    Very nice but i get the error - "The type or namespace name 'ActionTracking' could not be found (are you missing a using directive or an assembly reference?". Iv'e added the namespace "using GoogleAnalyticsTracker" aswell as using Google; but the error remain. Why is this? Thanks

  7. Avatar for Maarten Balliauw
    Maarten Balliauw February 17th, 2014

    Namespaces have changed, try finding the Tracker class.

  8. Avatar for Kristoffer Andersson
    Kristoffer Andersson February 17th, 2014

    problem solved (i think). using GoogleAnalyticsTracker.Web.Mvc. Btw how did you make it appear in the browser like the images. I mean a view, html etc .. Thanks

  9. Avatar for Fred .
    Fred . August 26th, 2014

    I installed the GoogleAnalyticsTracker.WebAPI2 package (version 3.0.16). When I try to add the filter to my FilterConfig.cs file, it says: "Cannot resolve symbol 'ControllerDescriptor'" on the action.ControllerDescriptor.ControllerName line.
    I can get rid of that that unresolved symbol by changing that line to action.ControllerContext.ControllerDescriptor.ControllerName.
    I still get an InvalidOperationException though.

    Additional information: The given filter instance must implement one or more of the following filter interfaces: System.Web.Mvc.IAuthorizationFilter, System.Web.Mvc.IActionFilter, System.Web.Mvc.IResultFilter, System.Web.Mvc.IExceptionFilter, System.Web.Mvc.Filters.IAuthenticationFilter.
  10. Avatar for Fred .
    Fred . August 26th, 2014

    How do you register ActionTrackingAttribute as a global action filter in a Web API 2 project?

    The 'ControllerDescriptor' could not be resolved. The 'action' lambda does not contain any 'ControllerDescriptor'.

  11. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    Which Web API version is this you are using?

  12. Avatar for Fred .
    Fred . August 26th, 2014

    Microsoft ASP.NET Web API 2.2

  13. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    Just tried installing googleanalyticstracker.webapi2, works smoothly? Also, do make sure the filter is added to the Web API filter collection and not the MVC one, whichcould be causing this error.

  14. Avatar for Fred .
    Fred . August 26th, 2014

    What do you mean?
    I added it to the App_Start\FilterConfig.cs file.

  15. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    That's the filter config for MVC. Try the App_Start\WebApiConfig.cs

  16. Avatar for Fred .
    Fred . August 26th, 2014

    I see. I didn't know this about the filter config is specific to MVC and WebApiConfig is specific to the Web API.

    It still says it cannot resolve ControllerDescriptor.

    Are you sure it should be action.ControllerDescriptor.ControllerName and not action.ControllerContext.ControllerDescriptor.ControllerName ?

    Because the first doesn't resolve, but then I discovered that the other one resolves.

  17. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    Could you provide me a stack trace of the exception?

  18. Avatar for Fred .
    Fred . August 26th, 2014

    I don't get any exception when using putting it in the WebApiConfig.cs as you told me to.

    I did get an exception earlier though when I foolishly put it into the FilterConfig.cs file though.

    But when I put action.ControllerDescriptor.ControllerName into the WebApiConfig.cs file it does not resolve the ControllerDescriptor in action.

    However, if I change action to action.ControllerContext so it becomes action.ControllerContext.ControllerDescriptor.ControllerName then
    action.ControllerContext resolves.

  19. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    Can you post me the relevant piece of code? I have no idea where you ware adding this :-)

  20. Avatar for Fred .
    Fred . August 26th, 2014

    App_Start\WebApiConfig.cs

    Works. Uses action.ControllerContext.ControllerDescriptor.ControllerName


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Web.Http;
    using GoogleAnalyticsTracker.WebApi2;
    using Microsoft.Owin.Security.OAuth;
    using Newtonsoft.Json.Serialization;

    namespace throwaway_webapi
    {
    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
    // Web API configuration and services
    // Configure Web API to use only bearer token authentication.
    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    config.Filters.Add(new ActionTrackingAttribute(
    "UA-XXXXXX-XX", "www.example.org",
    action => action.ControllerContext.ControllerDescriptor.ControllerName == "Api")
    );

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
    }
    }
    }

    Does not resolve symbol ControllerDescriptor.
    Uses action.ControllerContext.ControllerName


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Web.Http;
    using GoogleAnalyticsTracker.WebApi2;
    using Microsoft.Owin.Security.OAuth;
    using Newtonsoft.Json.Serialization;

    namespace throwaway_webapi
    {
    public static class WebApiConfig
    {
    public static void Register(HttpConfiguration config)
    {
    // Web API configuration and services
    // Configure Web API to use only bearer token authentication.
    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
    config.Filters.Add(new ActionTrackingAttribute(
    "UA-XXXXXX-XX", "www.example.org",
    action => action.ControllerDescriptor.ControllerName == "Api")
    );

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
    );
    }
    }
    }
  21. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    So you now have it working?

  22. Avatar for Fred .
    Fred . August 26th, 2014

    I think so. I assume so. Because it compiles and runs.

    Your instructions said to use action.ControllerDescriptor.ControllerName which did not work for me (not does resolve symbol, hence does not compile). Are you sure it should be that?

    Are you sure it should not be action.ControllerContext.ControllerDescriptor.ControllerName?
    This seems seems to work for me. At least it compiles (and runs without throwing any exception).

  23. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    The differences between ASP.NET Web API versions... Will update the readme accordingly.

  24. Avatar for Fred .
    Fred . August 26th, 2014

    Thank you. Please make sure you specify which config file too. I was not aware that FilterConfig was specific to MVC, hence I incorrectly tried to put my filter in the FilterConfig, when I should have been placed in the WebApiConfig file. This naturally resulted in an exception.

  25. Avatar for Fred .
    Fred . August 26th, 2014

    I would like to track user agents and user languages.

    I saw this could be accomplished using the the Tracker object and with the TrackPageView extension method.

    However, I was wondering if it it possible to accomplish this when registering ActionTrackingAttribute as a global action filter?
    Since I would like to avoid cluttering my controller actions.

  26. Avatar for Maarten Balliauw
    Maarten Balliauw August 26th, 2014

    You could create an overload of the attribute and override the OnTrackingAction
    method to add whatever you want to the tracker object.

  27. Avatar for Fred .
    Fred . August 27th, 2014

    Why the Ms-PL? Not even Microsoft themselves use this much more since it was a vanity license that nobody really liked.
    Microsoft mostly use the Apache License nowadays.

  28. Avatar for Maarten Balliauw
    Maarten Balliauw August 27th, 2014

    MS-PL is pretty unrestricted too, should be good.

  29. Avatar for Miša Zdravković
    Miša Zdravković November 24th, 2014

    Hi there. What is the recommended way to track how long a particular action was taking to run. I would need to measure a time how long each WebApi action runs. Is there a way collect this type of information and to pass it to Google Analytics? Thanks.

  30. Avatar for Yogesh Potdar
    Yogesh Potdar January 16th, 2015

    I am getting the following error, I have tried it by both ways.

    [ActionTracking("UA-XXXXXXX-XX")]
    public class MyDataController : Controller
    {
    ...
    }

    And

    // GA Tracking Code for Tracking MyData Controller
    filters.Add(new ActionTrackingAttribute(
    "UA-XXXXXXX-XX","www.mysite.com",
    action => action.ControllerDescriptor.ControllerName == "MyData")
    );

    I am getting the following error - Can you help me out ?

    An asynchronous module or handler completed while an asynchronous operation was still pending. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
    Stack Trace:

    [InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]

    Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.34009

  31. Avatar for envisiondata
    envisiondata June 4th, 2015

    I am using your tracker with Web API and when I look in Google Analytic all I see is country and state where my server is set up at. Not where the user is coming from. Is there a way to see the user location?

  32. Avatar for Maarten Balliauw
    Maarten Balliauw June 4th, 2015

    Try the latest 4.0.0 (prerelease)

  33. Avatar for envisiondata
    envisiondata June 4th, 2015

    I downloaded the latest version from https://github.com/maartenb... and rebuilt it with Visual Studio 2013 and am still getting the same information, country and city are where my AWS box is located. I am using the ActionFilter method on my class. I also tried the ActionMethod on an action with the same result. Should I be using it differently?

    GoogleAnalyticsTracker.Core.dll v4.0.0.0
    GoogleAnalyticsTracker.WebAPI.dll v4.0.0.0

  34. Avatar for Maarten Balliauw
    Maarten Balliauw June 4th, 2015

    There is an overload (not on my PC atm) that accepts additional parameters. You may want to use googleanalyticstracker.simple and build your own filter.

  35. Avatar for Tejaswi Rana
    Tejaswi Rana February 23rd, 2016

    How long does it take to update this? I'm running WebAPI2 in localhost and there's no exception. I've got the controller names correct too. I just don't see any updates in GA.

  36. Avatar for James
    James May 13th, 2016

    Are you going to add city and state the the tracking? Currently I have this installed on AWS and the only city state combination I see is where the AWS server is located. Maybe I'm missing something.

  37. Avatar for Marley
    Marley July 16th, 2021

    Sessions are also untracked: every event that is tracked counts as a new unique visitor to Google Analytics. If you do need to track user sessions, implement a custom IAnalyticsSession and pass it to the constructor of the tracker. Looking for a sample code to implement custom IAnalyticsSession. Working on google analytics tracker, my problem is i would want to track users and sessions therefore at the moment every event tracked is counted as a unique user. I kindly need help on this, anyone?