Easily Instrument HTTP Calls in Your Apps

Lately I've been doing a lot of work around adding instrumentation to basically all the things, which naturally includes my apps. It's common practice to instrument things on the server-side of the API using awesome tools like NewRelic, but that's only part of the equation. Obviously we need the server to respond as quickly as possible so being aware of that metric is essential, but on top of that the user also has to wait for that request to get to the server and back over networks that are often pretty slow. How long are you making users watch a loading spinner for when they're trying to get something done?

Depending on what your apps are doing it probably makes sense to instrument more than just HTTP calls to be able to get some visibility into this, but I'll just start with HTTP here. When I was spiking some ideas on how to implement this I went down a few different paths before I realized I was overlooking the most obvious/simple solution that we'll go through here.

HttpClient and HttpMessageHandler

If you're using HttpClient to make your HTTP calls (if you're not, you probably should be!), you may or may not know that it has a constructor that takes in a HttpMessageHandler instance. The awesome part about that is that it means we can create a simple class that will be inserted into the pipeline of every HTTP call being made from that HttpClient instance.

Here's what that a simple implementation might look like:

class InstrumentedHttpClientHandler : HttpClientHandler  
{
  protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var successful = false;
    var stopwatch = Stopwatch.StartNew();

    try
    {
      var result = await base.SendAsync(request, cancellationToken);
      successful = result.IsSuccessStatusCode;

      return result;
    }
    finally
    {
      stopwatch.Stop();

      // TODO: publish metric
    }
  }
}

This implementation simply wraps the base implementation of SendAsync with a stopwatch so the behavior will be the same except that now you'll get access to how long the call took via stopwatch.ElapsedMilliseconds, and whether or not it was successful. One thing to be careful of when adding instrumentation is that you don't want to slow things down any more than necessary in order to measure it. In this case there's minimal overhead, especially when dealing with network calls that will reliably take a decent chunk of time anyway.

With this data you can now publish your metrics in whatever way makes sense for your application, be it to the device logs, an API, etc. Not only is it useful to know how you're performing right now, it's even more important to know how you're trending over time? Did your last release make things better or worse? Without tracking it you'll never know.

ModernHttpClient

If you're using ModernHttpClient (again, if you're not, you should be!) you may have noticed that this is the same approach used by that library to intercept requests happening in HttpClient. You can still take advantage of this instrumentation approach if you're using ModernHttpClient. In fact, all you need to do is change the base class of InstrumentedHttpClientHandler to ModernHttpClient's NativeMessageHandler and you're all set!

comments powered by Disqus
Navigation