Using Azure Application Insights with aiohttp

In the last months I wrote an alternative Python SDK for Azure Storage using aiohttp, to take advantage of asynchronous web requests. Recently I did the same for Azure Application Insights. For those who don’t know it, Application Insights is a monitoring service that enables collecting telemetry data from applications, such as custom events and traces, unhandled exceptions, web requests, page views and much more.

It provides live stream of all metrics and possibility to receive alerts when desired, for example email notifications when the average response time for web requests is higher than x seconds in the last y minutes.

As it happens for Azure Storage, Microsoft provides an official Python SDK for Application Insights, but it is designed to work with Python 2.x and 3.x, therefore not using asyncio infrastructure for event loops and not optimal for application using asyncio. For this reason, I wrote an alternative library and published it in GitHub and PyPi, with name asynapplicationinsights.

The library can be installed using pip:

pip install asynapplicationinsights

This post describes how the library was written. To know more about Application Insights, I recommend the following article from Microsoft:

1. Analysing the Application Insights REST api

The first step was to collect information about the Application Insights REST api, to know what parameters must be sent to collect telemetry. I didn’t find this information inside MSDN documentation, so I did the following:

  • installed the official Python SDK from Microsoft in a python virtual environment
  • detected the function making web requests to Application Insights in the source code, and modified it to log request bodies on file system
  • used the library, calling methods to log events, requests, traces, etc.

I could also have sniffed traffic generated by the official .NET SDK for Application Insights, but I personally find easier to just edit a couple of lines in Python.

The file to change in the official SDK is: applicationinsights/channel/SenderBase.py, below a fragment of code showing which lines were added to log request bodies.

def send(self, data_to_send):
    """ Immediately sends the data passed in to :func:`service_endpoint_uri`. If the service request fails, the
    passed in items are pushed back to the :func:`queue`.

    Args:
        data_to_send (Array): an array of :class:`contracts.Envelope` objects to send to the service.
    """
    request_payload = json.dumps([ a.write() for a in data_to_send ])

    # added lines, logs request payload for each object type:
    for item in data_to_send:
        name = item.name
        with open(name + '.json', mode='wt') as body_log:
            body_log.write(json.dumps([item.write()], indent=2))

For example, the payload to log a web request has the following structure:

[
  {
    "ver": 1,
    "name": "Microsoft.ApplicationInsights.Request",
    "time": "2018-06-23T22:56:17.393658Z",
    "sampleRate": 100.0,
    "iKey": "<INSTRUMENTATION KEY HERE>",
    "tags": {
      "ai.device.id": "device_id",
      "ai.device.locale": "en_US",
      "ai.device.osVersion": "#25-Ubuntu SMP Wed May 23 18:02:16 UTC 2018",
      "ai.device.type": "Other",
      "ai.internal.sdkVersion": "py3:0.11.4"
    },
    "data": {
      "baseType": "RequestData",
      "baseData": {
        "ver": 2,
        "id": "dc0d1d70-41dc-4216-892c-c043f0575dd8",
        "name": "AddToFavorites",
        "duration": "00:00:00.125",
        "responseCode": 201,
        "success": true,
        "url": "/api/v1/favorites"
      }
    }
  }
]

And the payload to log a trace event the following:

[
  {
    "ver": 1,
    "name": "Microsoft.ApplicationInsights.Event",
    "time": "2018-06-23T22:56:17.392930Z",
    "sampleRate": 100.0,
    "iKey": "<INSTRUMENTATION KEY HERE>",
    "tags": {
      "ai.device.id": "device_id",
      "ai.device.locale": "en_US",
      "ai.device.osVersion": "#25-Ubuntu SMP Wed May 23 18:02:16 UTC 2018",
      "ai.device.type": "Other",
      "ai.internal.sdkVersion": "py3:0.11.4"
    },
    "data": {
      "baseType": "EventData",
      "baseData": {
        "ver": 2,
        "name": "Fart",
        "properties": {
          "foo": "foo"
        },
        "measurements": {
          "speed": 60.5
        }
      }
    }
  }
]

It is sufficient to make HTTP POST requests to https://dc.services.visualstudio.com/v2/track endpoint, with valid body and instrumentation key, to collect information in Application Insights. The instrumentation key is a GUID assigned to a given instance of Application Insights service.

Additional information about these data structures and context tags can be found here in GitHub:

When information are logged to Application Insights, they can be explored using a REST api or visually on Azure Portal. Pictures below show the convenient graphs (failed requests, server response time, page view load time, server requests) and the analytics portal, supporting user defined queries.

Graphs

Analytics Portal

2. Writing the SDK for asyncio

The second step was to write code to make asynchronous web requests to Application Insights REST api. For the implementation I used aiohttp library and its HTTP client. My library uses a queue and flushes messages when the queue has 500 items in memory, or when explicitly required. This follows the same features of the official Python SDK from Microsoft, with the difference that is using asyncio. This client can be used in any kind of application, even in console applications having access to the internet. Examples for using this asynchronous client are included in the asynapplicationinsights GitHub repository, like in the code below.

import uuid
import asyncio
import unittest
from datetime import datetime, timedelta
from asynapplicationinsights.telemetry import AsyncTelemetryClient
from asynapplicationinsights.channel.aiohttpchannel import AiohttpTelemetryChannel
from random import randint


instrumentation_key = '<YOUR_INSTRUMENTATION_KEY>'

# This is just an example, for more information see the GitHub repository
# https://github.com/RobertoPrevato/asynapplicationinsights
#
class TestAi(unittest.TestCase):

    def test_write_event(self):
        loop = asyncio.get_event_loop()

        async def go():
            client = AsyncTelemetryClient(instrumentation_key,
                                          AiohttpTelemetryChannel())

            await client.track_event('Example',
                                     measurements={
                                         'speed': 55.5
                                     })

            await client.track_trace('Foo', {'foo': 'power'}, 2)

            for _ in range(10):
                await asyncio.sleep(0.005)
                await client.track_metric('Something',
                                          randint(0, 200))

            await client.track_request(str(uuid.uuid4()),
                                       'Example',
                                       'http://localhost:44666/like/123456',
                                       True,
                                       datetime.utcnow() + timedelta(seconds=-10),
                                       duration=125,
                                       response_code=200,
                                       http_method='GET'
                                       )

            try:
                x = 100 / 0
            except:
                await client.track_exception()

            await client.flush()
            await client.dispose()

        loop.run_until_complete(go())

3. A middleware for aiohttp server

Finally, I included an implementation of middleware for aiohttp servers, to track web requests and unhandled exceptions. The middleware also logs how much time is spent inside the code for each request, so it is useful to detect bottlenecks. Instructions for use are included in a dedicated page of the project wiki.

As a final note, for single page applications (SPA), the server side middleware should be used together with the official JavaScript SDK from Microsoft because certain kinds of telemetries are designed to be coming from browsers (for example, PageView, not implemented in my asynapplicationinsights). In case you are wondering, requests are not logged twice in this case: the JavaScript library is not generating logs of Request type.

Conclusions

Using the Application Insights api directly can be useful in some scenarios, so knowing its data structures and the shape of requests can be useful. Writing a custom client helped me learning many things I didn’t know about Application Insights, for example how users and sessions are logged and displayed on the Azure portal.

Written on June 24, 2018

Roberto Prevato

Italian graphic designer, applications architect, DevOps, web applications specialist, artist wannabe.
Metal head fond of philosophy and arts.