Exporting OpenTelemetry Traces to Azure Monitor using ASP.NET Core

OpenTelemetry is an observability framework that provides APIs, libraries, agents, and instrumentation to collect distributed traces and metrics from an application. It's an open-source, CNCF project that aims to standardize the generation and collection of telemetry data (metrics, logs, and traces) for cloud-native software.

In this article, you will learn how to export custom OpenTelemetry traces from your ASP.NET Core applications to Azure Monitor.

Here are the steps to follow along.

  • Create a new Application Insights resource in Azure. You can use the detailed instructions in this Microsoft Learn guide to create the resource using the Azure Portal or Azure CLI. Please copy the connection string for the resource as you will need it later.
    Azure Portal
  • Create a new ASP.NET Core Minimal API project using your preferred IDE or the .NET CLI. Please follow the instructions in this Microsoft guide to create the project. Use an officially supported version of .NET Core to avoid any issues.
  • Install the latest version of the following NuGet packages in your project.
    dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
  • Replace the contents of the Program.cs file with the following code.
    using System.Diagnostics;
    using Azure.Monitor.OpenTelemetry.AspNetCore;
    using OpenTelemetry.Trace;
    
    var activitySource = new ActivitySource("OtelDemo.App", "1.0.0");
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Configure the OpenTelemetry tracer provider to add a source named "OtelDemo.App". This will ensure that all activities created by the activity source are traced.
    builder.Services.ConfigureOpenTelemetryTracerProvider((sp, tracerProviderBuilder) => tracerProviderBuilder.AddSource("OtelDemo.App"));
    
    // Add the Azure Monitor telemetry service to the application.
    // This service will collect and send telemetry data to Azure Monitor.
    builder.Services.AddOpenTelemetry().UseAzureMonitor();
    
    // Build the ASP.NET Core web application.
    var app = builder.Build();
    
    app.MapGet("/traces-demo", () =>
    {
        // Create a new activity named "parent". This will be recorded as a span.
        using var activity = activitySource.StartActivity("parent");
        // Create a new activity named "child" that is a child of the "parent" activity. This will be recorded as a child span.
        using var childActivity = activitySource.StartActivity("child");
        // Set a custom tag on the "child" activity. This can be any metadata that you want to associate with the span.
        childActivity?.SetTag("custom-tag", "custom-value");
    
        return "Custom spans recorded";
    });
    
    // Run the application.
    app.Run();
    

Let's go through the code to understand how it works.

Firstly, an ActivitySource instance is defined. This is used to start new activities that can be traced. The parameters are the name of the source and its version. The .NET instrumentation of OpenTelemetry uses the existing constructs of the System. Diagnostics API to be OpenTelemetry compliant. Hence, the .NET implementation of OpenTelemetry looks different from other language implementations due to this reuse. You can read more about the finer details of .NET instrumentation for OpenTelemetry here.

Next, a tracer provider is added to manage the lifecycle of tracers. Tracers are used to create spans, which represent units of work within a trace. A trace is a set of spans related to each other, typically because they represent a single operation and its sub-operations. The tracer provider ensures that tracers are correctly initialized and disposed of. It also provides configuration options for the tracers, such as setting the default sampler or adding span processors.

In the sample application, the tracer provider is configured to add a source named "OtelDemo.App". This means that it will create tracers that can begin new spans for activities created by the ActivitySource with that name. These spans will then be collected and exported by the OpenTelemetry SDK, which in this case is configured to send them to Azure Monitor.

Next, the Azure Monitor telemetry service is added to the application. This service collects and sends telemetry data to Azure Monitor.

Finally, to generate traces, we created an endpoint at "/traces-demo". When this endpoint is hit, it starts a parent activity and a child activity, sets a custom tag on the child activity, and then returns a string response. Since each activity corresponds to a span, this endpoint will generate two spans: a parent span and a child span with a custom tag.

We are ready to debug our application now, but before that, we have to configure the connection string for the Application Insights resource that we created earlier. To accomplish this, set the APPLICATIONINSIGHTS_CONNECTION_STRING environment variable value to the connection string that you copied previously. You can do this by running the following command in the terminal:

APPLICATIONINSIGHTS_CONNECTION_STRING=<connection-string>

Output

Debug the application and invoke the endpoint at /traces-demo. Every successful invocation of the endpoint will generate a trace with two spans a parent span and a child span. These spans will be exported to Azure Monitor.

To view the exported traces, visit the Azure Portal and navigate to the Application Insights resource that you created earlier. Next, click on the Transaction search tab and then on the request displayed in the list to view the details of the trace.

Transaction search

The following screenshot shows the details of the trace and the spans associated with it. You can click on the spans to view their details. You will notice that the child span contains the custom tag that we set earlier.

Details

To explore the integration between Azure Monitor and OpenTelemetry further, including the various types of telemetry that can be exported, please refer to the official documentation.