Skip to content

Improve Application Insights integration for in-process Azure Function v4.

License

Notifications You must be signed in to change notification settings

gabrielweyer/azure-functions-telemetry

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Azure Functions Telemetry

🚨 The customisation supports in-process Functions only. Isolated Functions are not supported.

Beginning 10 November 2026, the in-process model for .NET apps in Azure Functions will no longer be supported. Migrate to the isolated worked model.

The Application Insights integration for Azure Functions v4 suffers from a few quirks that can lead to a huge Application Insights bill:

  • Telemetry processors are not supported, preventing developers from discarding telemetry items
  • Each Function execution records a trace when starting and on completion
  • Exceptions are logged twice for the HTTP binding
  • Exceptions are logged three times for the Service Bus binding

The next issue has no impact on the cost of Application Insights but is related to the development experience. TelemetryConfiguration is not registered in the Inversion Of Control container when the Application Insights connection string is not set. Emitting a custom metric requires injecting the TelemetryConfiguration. Running locally without having configured the Application Insights connection string will then result in an exception.

The last issue is not related to Application Insights but also negatively affects developers' productivity. The custom Console logger provider used by the Azure Functions runtime does not include the stack trace when displaying an exception (for the HTTP binding at least).

If you are not familiar with some of the more advanced features of Application Insights, I suggest you go through the below references before reading the rest of this document:

In this repository I have tried to address all the quirks listed above.

🚨 The customisation is experimental. There may be undesired side effects around performance and missing telemetry. If you are experiencing issues such as missing telemetry, no exception being recorded when something does not work as expected I recommend disabling the custom integration and reproducing the problem without it. This can be done by creating an application setting named DisableApplicationInsightsCustomisation with a value of true.

Table of contents

Using the Application Insights customisation

The customisation supports the v4 runtime.

Build Status NuGet

dotnet add package AzureFunctions.Better.ApplicationInsights

For the most basic integration, you need to provide:

  • {ApplicationName} used to set Application Insights' Cloud role name (optional). When not provided, the default behaviour is preserved (the Cloud role name will be set to the Function App's name)
  • {TypeFromEntryAssembly} typically would be typeof(Startup). When {ApplicationName} is provided, I read the Assembly Informational Version of the entry assembly to set Application Insights' Application version (I use unknown as a fallback). When {ApplicationName} is not provided, Application version will not be present on the telemetry items

In your Startup class add the below snippet:

var appInsightsOptions = new CustomApplicationInsightsOptionsBuilder(
        "{ApplicationName}",
        {TypeFromEntryAssembly})
    .Build();

builder.Services
    .AddCustomApplicationInsights(appInsightsOptions)
    .AddCustomConsoleLogging();

What do I get?

Discarding Function execution traces

This is implemented by FunctionExecutionTracesFilter and enabled by default.

This can be disabled by setting the application setting ApplicationInsights:DiscardFunctionExecutionTraces to true.

Discarding duplicate exceptions

This is implemented by DuplicateExceptionsFilter and always enabled.

Discarding health requests

This is enabled by setting the application setting ApplicationInsights:HealthCheckFunctionName and supplying the Function name (the argument provided to the FunctionNameAttribute). The telemetry processor used is HealthRequestFilter.

Better Service Bus binding "request"

The URL and response code are not being set on the service bus triggered "requests". The ServiceBusRequestInitializer will do this for you.

  • URL: I use the Function name
  • Response code: 200 in case of success, 500 in case of failure

The ServiceBusRequestInitializer is always enabled.

Discarding Service Bus trigger traces

This is recommended on high-volume services. This is disabled by default.

This is done by setting the application setting ApplicationInsights:DiscardServiceBusTrigger to true. The telemetry processor used is ServiceBusTriggerFilter.

Replacing the Console logging provider

This is done by calling AddCustomConsoleLogging. You will then consistently get stack traces in the console.

Registering telemetry initializers

πŸ“ The built-in integration supports telemetry initializers. The custom integration supports registering telemetry initializers in the same way as the built-in integration does.

Telemetry initializers can either be registered using TImplementation:

builder.Services.AddSingleton<ITelemetryInitializer, YourInitializer>();

Or an instance of the telemetry initializer:

// Use:
builder.Services.AddSingleton<ITelemetryInitializer>(new YourOtherInitializer("NiceValue"));
// Do not use, otherwise your telemetry initializer will not be called:
builder.Services.AddSingleton(new YourOtherInitializer("NiceValue"));

You can add as many telemetry initializers as you want.

Telemetry processors support

πŸ“ The built-in integration does not support telemetry processors. I have added support so that you can use the same extension method than when registering a telemetry processor in ASP.NET Core:

builder.Services.AddApplicationInsightsTelemetryProcessor<YourTelemetryProcessor>();

You can add as many telemetry processors as you want.

Configuration

The customisation is configured using application settings:

{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "DisableApplicationInsightsCustomisation": true, // Allows you to disable the customisation if you think something is wrong with it
    "ApplicationInsights:DiscardServiceBusTrigger": true,
    "ApplicationInsights:HealthCheckFunctionName": "HealthFunction", // The name of the Function, not the route
    "ApplicationInsights:DiscardFunctionExecutionTraces": false // Allows you to stop discarding Function execution traces
  }
}

If you don't want to use the ApplicationInsights key, you can provide another value when configuring the customisation:

var appInsightsOptions = new CustomApplicationInsightsOptionsBuilder(
        "{ApplicationName}",
        {TypeFromEntryAssembly})
    .WithConfigurationSectionName("SomeOtherSectionName")
    .Build();

Limitations

Setting the cloud role name breaks the Function's Monitor blade.

Broken Function's Monitor blade

The Azure web jobs SDK supports overriding the cloud role name by setting an environment variable named WEBSITE_CLOUD_ROLENAME. When this environment variable is set and the value is not the Function App's name, the Function's Monitor blade is broken as well.

Migration guides

Demo

A demo demonstrates all the customisation's features.

Q&A

Why so much code to support telemetry processors?

There is an opened GitHub issue about the lack of telemetry processors support in Azure Functions. The thread supplies a workaround to enable telemetry processors, but the telemetry processors added in this fashion will not be called for request telemetry items.

Appendix

Software versions

The latest version of the Azure Functions Core Tools I have been using is 4.0.6610.

NuGet packages:

Supporting telemetry processors

The code in AddCustomApplicationInsights retrieves the configured built-in telemetry processors, adds them to a new telemetry processor chain and builds the chain. This gives me the opportunity to add our own processors to the chain.

The first built-in processor in the chain is OperationFilteringTelemetryProcessor, this processor discards all the dependencies considered internal to the Azure Functions runtime (such as access to blob storage for the distributed lock and the calls to Azure Service Bus for the Service Bus binding).

One of the side-effects of the approach I am using is that the Azure Functions runtime will reference the initial instance of OperationFilteringTelemetryProcessor and will call it directly when tracking requests manually. Normally the OperationFilteringTelemetryProcessor instance points to the second processor in the chain (QuickPulseTelemetryProcessor). One way for our processors to be called is to point the existing OperationFilteringTelemetryProcessor instance to our first processor and point our last processor to QuickPulseTelemetryProcessor. This is done through some pretty dodgy untested code, but it works ℒ️.

As I did not manage to cover my customisation with unit tests, I wrote integration tests instead.