Custom Azure Application Insights Telemetry Metrics with .NET

Custom Azure Application Insights Telemetry Metrics with .NET

By default, Azure Application Insights comes with necessary and important telemetry capabilities to monitor the basic functions of an application and collect telemetry.

In almost all cases, however, it is necessary or at least advisable that custom metrics are also collected and transmitted to Application Insights in order to understand and monitor the functionality of an application.

App Insights Custom Events

Custom metrics can be collected in Azure Application Insights via custom events. A custom event is an event that is triggered by an application and sent to Application Insights. A custom event can also contain additional data that can be used for analysis and visualization in Application Insights.

The SDK provides the following APIs:

  • TrackEvent - Sends a custom event to Application Insights.
  • TrackMetric - Sends a user-defined metric to Application Insights.
  • TrackTrace - Sends a custom track to Application Insights.
  • TrackException - Sends a custom exception to Application Insights.
  • TrackRequest - Sends a custom request to Application Insights.
  • TrackDependency - Sends a user-defined dependency to Application Insights.

The latter is the variant we are particularly interested in for our Custom Metrics request. This not only allows me to monitor how individual code blocks are executed, but also monitor the duration so that I can identify performance or runtime problems.

Custom Dependency

The Custom Dependency is a direct functionality of the Telemetry Client and can be easily used through Dependency Injection as soon as Application Insights has been registered accordingly.

public class MyClass
{
    private readonly TelemetryClient _telemetryClient;

    public MyClass(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public async Task MyMethod()
    {
       string operationName = nameof(MyMethod);

       IOperationHolder<DependencyTelemetry> aiOperation =
          _telemetryClient.StartOperation<DependencyTelemetry>(operationName);

       try
       {
            // await  ... my actual code

            aiOperation.Telemetry.Success = true;
       }
       catch(Exception e)
       {
            aiOperation.Telemetry.Success = false;
            _telemetryClient.TrackException(e);

            throw;
       }
       finally
       {
            _telemetryClient.StopOperation(aiOperation);
       }
    }
}

We therefore have an OperationHolder that contains the Telemetry information. This is started by StartOperation and ended by StopOperation. If our code is executed successfully, the client is informed that the operation was successful and the TrackException also transmits the exception to Application Insights in the event of an error.

In this example, tracking is directly part of the method, but depending on the architecture of the application, it should take place in a middleware or another central point of the application. If you use MediatR as an application engine, you can implement this using a Behavior.

Own Monitoring Implementation

To enable tracking in your own context, I have created this simplified implementation (optimized accordingly in my code, kept simple here):

public class DepenencyOperationMonitor
{
    private readonly TelemetryClient _telemetryClient;

    public DepenencyOperationMonitor(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public async Task<T> ExecuteAsync<T>(Func<Task<T>> action, string operationName)
    {
        using  IOperationHolder<DependencyTelemetry> aiOperation =
            _telemetryClient.StartOperation<DependencyTelemetry>(operationName);

        try
        {
            T response = await action().ConfigureAwait(false);

            // no exception = succeeded
            aiOperation.Telemetry.Success = true;

            return response;
        }
        catch (Exception e) 
        {
            aiOperation.Telemetry.Success = false;
            _telemetryClient.TrackException(e);

            throw;
        }
        finally
        {
            _telemetryClient.StopOperation(aiOperation);
        }
    }
}

This OperationMonitor can now simply be registered as a singleton and injected into your own classes.

public class MyClass
{
    private readonly IDepenencyOperationMonitor _operationMonitor;

    public MyClass(IDepenencyOperationMonitor operationMonitor)
    {
        _operationMonitor = operationMonitor;
    }

    public async Task<MyResult> MyMethod()
    {
        const string operationName = $"{nameof(MyClass)}.{nameof(MyMethod)}";

        MyResult result = await _operationMonitor.ExecuteAsync(
            async () =>
            {
              // await  ... my actual code

                return myResult;
            }, operationName).ConfigureAwait(false);

        return result;
    }

Dashboard View

After the data has been sent to Application Insights, it can be viewed in the Azure Portal. Here you can see the duration of the individual operations and also filter them according to the success or failure of the operation.

For that, open your App Insights Dashboard and navigate to the Dependency Tab.

2024-02_AppInsights-Dashboard

In this view you can also see that this behavior is included in many other software modules, e.g. in all Azure SDKs!

Conclusion

With the help of the TelemetryClient and the IOperationHolder we can easily monitor our own code and send the results to Application Insights. This allows us to monitor the performance of our application and identify runtime problems.

Have fun monitoring your application!