Friday, April 22, 2022

Application Insights telemetry in Azure Functions

Suppose you are developing an Azure Function App, that contains various functions, with critical code about an important workload you want to run in your environment. Because of this, you need to be able to monitor your functions executions and send the collected logs and metrics to an Application Insights resource in Azure, to drill down and investigate about problems you may encounter or measure the performance of your functions and their dependencies.


In today’s post we are going to see how we can enable application insights integration into a Functions App, both when running in Azure and in our local development environment. We are also going to extend the built-in Application Insights functionality, by writing some custom code to track custom telemetry items in certain scenarios.


First and foremost, Azure Functions offers built-in integration with Application Insights to better enable you to monitor your function apps. Application Insights, a feature of Azure Monitor, is a service that collects data generated by your function app, including information your function app writes to logs. All these are provided to you out of the box.


Application Insights integration is enabled when your function app is created, either you do this from the Azure Portal or you just publish it for the first time from Visual Studio / Visual Studio Code. In both situations you will be given the chance to enable and configure the built-in integration with a specified Application Insights resource of your choice. Below you can see a snapshot of a Function App creation wizard workflow inside the Azure Portal, where you are eventually prompted to enable the Application Insights built-in integration in the “Monitoring” tab of the creation wizard:



Figure 1 - Enable Application Insights in Create Function App Flow in Azure Portal


On the other hand, if you have an existing function app that doesn't have the instrumentation key already set, you must first enable Application Insights integration. This can be simply done again through Azure Portal, by going to the “Settings -> Application Insights” section of the function app resource:



Figure 2 - Enable Application Insights in an existing Function App in Azure Portal


As Application Insights instrumentation is built into Azure Functions, you just need a valid instrumentation key to connect your function app to an Application Insights resource of your choice. When you enable application insights, like the two examples we saw above, what Azure eventually does, is to add the instrumentation key to the function app’s application settings. The key must be in an app setting named APPINSIGHTS_INSTRUMENTATIONKEY.



Figure 3 - INSTRUMENTATION_KEY in Function App Settings


With that in mind, let’s now think about how we can achieve collecting telemetry data from the same function app that runs in our local development environment while we are debugging. If we want to also take this telemetry data and send it to Azure, then we just need to take the instrumentation key of our choice and put it inside local.settings.json file of our Function App. There is no security concern here, because this particular file is excluded from being committed to source control.



Figure 4 - APPINSIGHTS_INSTRUMENTATIONKEY in local.settings.json


Now let's move our attention to a different scenario. As part of your function app code, you will probably need to call some external out-of-process dependencies. A dependency is a component that is called by your application. It's typically a service called using HTTP, or a database, or a file system. These could be for example: SQL databases, HTTP/HTTPS calls, Azure Service Bus / Storage Queue and others. 


Application Insights SDKs ships with a specific Telemetry Module that automatically collects dependencies. This dependency collection is brought and enabled automatically for Function App applications. What this module does, among others, is to measure the duration of dependency calls, whether it's failing or not, along with additional information like name of dependency. Some of those external dependencies may not be tracked automatically by Application Insights. At the time the current article is written, the following are some examples of dependencies, which aren't automatically collected, and hence require manual tracking:

  • Azure Cosmos DB is tracked automatically only if HTTP/HTTPS is used. TCP mode won't be captured by Application Insights
  • Redis

For those dependencies not automatically collected, you can track them manually using the TrackDependency API. Let's see an example here. Suppose we have an Azure Function project and in one of our functions defined there, we want to call to an Azure Redis Cache to save a particular string. As we are going to send a request to an Azure Redis Cache instance, we need to install the Microsoft.Extensions.Caching.StackExchangeRedis nuget package. Also, we will install the Microsoft.Azure.Functions.Extensions package as well, as we need to register the Redis related services to the DI container. To be able to register our own services in the DI framework, we need to create a Startup class, and put the following code inside of it:



Figure 5 - DI in Azure Functions and Startup.cs


Here we can see that we are registering the Redis Cache required services by calling .AddStackExchangeRedisCache() method and passing the Azure Redis Cache connection string via reading an environment variable called REDISCACHE_CONNECTIONSTRING. To be able to run it locally and in Azure we also of course need to register this environment variable in local.settings.json file for local development scenario and in App Settings of the Azure Function App configuration blade for when the code will run in Azure (just like we did above with APPINSIGHTS_INSTRUMENTATIONKEY).


We are also registering a service called ResponseCacheService as a singleton in our DI container. This class is just a wrapper around the redis cache service that is registered when calling the AddStackExchangeRedisCache() method, which provides an implementation for the IDistributedCache interface that we are injecting in the constructor. Our custom service contains just one method called CacheResponseAsync. You can see its contents in the snapshot below:



Figure 6 - ResponseCacheService.cs


Now, the call to _distributedCache.SetStringAsync in Line 27 actually sends an HTTP request to the Azure Redis Cache service. If we run this code now, see what happens in Application map view of our Application Insights resource instance that is connected to our Function App:



Figure 7 - Application map view before applying custom dependency telemetry tracking


We can see here that Application Insights are not able to capture automatically the call to Azure Redis Cache. To be able to do something like this, we need to follow some small steps and write a bit of code.


As we have already mentioned above, Application Insights is added by Azure Functions automatically. That basically means two things in our case. First, we don't need to call AddApplicationInsightsTelemetry() to the DI services collection of our Function App, because if we do we will register services that conflict with services provided out of the box by the Functions environment. Apart from that, we should also not register our own TelemetryConfiguration or TelemetryClient, because we are already using the built-in Application Insights functionality.


So, how do we actually track custom dependencies from our function app code? As it turns out, there is a Functions-specific version of the Application Insights SDK that you can use to send custom telemetry data from your functions to Application Insights. We just have to install the Microsoft.Azure.WebJobs.Logging.ApplicationInsights nuget package in our Azure Function App project. 


The last thing we need to do is to change the ResponseCacheService implementation a little bit and write some code, inject the TelemetryClient instance and use it, in order to track the dependency call to Azure Redis Cache. The code of the CacheResponseAsync method will now look like the following figure:



Figure 8 - CacheResponseAsync of ResponseCacheService enhanced with custom dependency tracking for Redis


The _telemetryClient and _logger properties that you can see in the picture above, are passed to the ResponseCacheService class through constructor injection:



Figure 9 - Inject TelemetryClient in ResponseCacheService constructor


Now if we run this code, let's see what happens in Application Map inside the Application Insights resource:



Figure 10 - Application map view after applying custom dependency telemetry tracking


As you can see, we are now able to track the Redis Cache dependencies and drill down to various statistics regarding that particular external dependency in our application. You can find the whole code for our example in this GitHub repo.

Posted at