Application Insights and OpenTelemetry in .NET

Observability in .NET has converged on OpenTelemetry. Microsoft now recommends OpenTelemetry as the instrumentation standard for .NET applications, with Application Insights as the backend for viewing and querying your telemetry data. The old Application Insights SDK is in maintenance mode — new projects should use OpenTelemetry from the start.

The Architecture

OpenTelemetry provides three signals:

The Azure Monitor OpenTelemetry exporter sends all three to Application Insights. Your code instruments with OpenTelemetry APIs, and the exporter handles the rest.

Setting Up

Install Azure.Monitor.OpenTelemetry.AspNetCore — this single package bundles everything:

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .UseAzureMonitor(options =>
    {
        options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
    });

var app = builder.Build();

This configures:

For most applications, this single call gives you production-ready observability.

Custom Traces with ActivitySource

.NET's System.Diagnostics.Activity maps directly to OpenTelemetry spans. Create an ActivitySource for your application:

Example.cs
public class OrderService
{
    private static readonly ActivitySource ActivitySource = new("MyApp.Orders");
    private readonly ILogger<OrderService> _logger;
    private readonly OrderRepository _repository;

    public OrderService(ILogger<OrderService> logger, OrderRepository repository)
    {
        _logger = logger;
        _repository = repository;
    }

    public async Task<Order> ProcessOrderAsync(OrderRequest request)
    {
        using var activity = ActivitySource.StartActivity("ProcessOrder");
        activity?.SetTag("order.customer_id", request.CustomerId);
        activity?.SetTag("order.item_count", request.Items.Count);

        var order = new Order(request);

        using (var validationActivity = ActivitySource.StartActivity("ValidateOrder"))
        {
            await ValidateAsync(order);
            validationActivity?.SetTag("order.valid", true);
        }

        using (ActivitySource.StartActivity("SaveOrder"))
        {
            await _repository.SaveAsync(order);
        }

        _logger.LogInformation("Order {OrderId} processed for customer {CustomerId}",
            order.Id, order.CustomerId);

        activity?.SetTag("order.id", order.Id);
        return order;
    }
}

Register your ActivitySource so OpenTelemetry knows to capture it:

Example.cs
builder.Services.AddOpenTelemetry()
    .UseAzureMonitor()
    .WithTracing(tracing =>
    {
        tracing.AddSource("MyApp.Orders");
    });

In Application Insights, this appears as a nested trace: the HTTP request contains a ProcessOrder span, which contains ValidateOrder and SaveOrder child spans.

Custom Metrics

Define custom metrics using the System.Diagnostics.Metrics API:

Example.cs
public class OrderMetrics
{
    private readonly Counter<long> _ordersProcessed;
    private readonly Histogram<double> _orderValue;

    public OrderMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("MyApp.Orders");
        _ordersProcessed = meter.CreateCounter<long>(
            "orders.processed",
            unit: "{order}",
            description: "Number of orders processed");
        _orderValue = meter.CreateHistogram<double>(
            "orders.value",
            unit: "GBP",
            description: "Order value distribution");
    }

    public void RecordOrderProcessed(decimal value, string status)
    {
        _ordersProcessed.Add(1, new KeyValuePair<string, object?>("status", status));
        _orderValue.Record((double)value);
    }
}

Register the meter:

Example.cs
builder.Services.AddSingleton<OrderMetrics>();

builder.Services.AddOpenTelemetry()
    .UseAzureMonitor()
    .WithMetrics(metrics =>
    {
        metrics.AddMeter("MyApp.Orders");
    });

These metrics appear in Application Insights under Custom Metrics, where you can chart them, set alerts, and build dashboards.

Structured Logging

ILogger output is automatically correlated with the active trace:

Example.cs
_logger.LogInformation("Processing order {OrderId} with {ItemCount} items",
    order.Id, order.Items.Count);

In Application Insights, this log entry is linked to the request trace. You can navigate from the log to the full distributed trace and back. The structured parameters (OrderId, ItemCount) are searchable dimensions.

Enriching Telemetry

Add custom properties to all telemetry from your application:

Example.cs
builder.Services.AddOpenTelemetry()
    .UseAzureMonitor()
    .WithTracing(tracing =>
    {
        tracing.AddProcessor(new ServiceEnrichmentProcessor());
    });
ServiceEnrichmentProcessor.cs
public class ServiceEnrichmentProcessor : BaseProcessor<Activity>
{
    public override void OnStart(Activity data)
    {
        data.SetTag("deployment.environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
        data.SetTag("service.version", typeof(Program).Assembly
            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
    }
}

Every trace now carries the deployment environment and service version, making it easy to filter telemetry by release.

Querying in Application Insights

Application Insights uses KQL (Kusto Query Language) for querying. Find slow orders:

kusto
dependencies
| where name == "ProcessOrder"
| where duration > 1000
| project timestamp, duration, customDimensions["order.customer_id"]
| order by duration desc
| take 50

The custom tags you set on your activities appear in customDimensions, making them queryable.

The Migration Path

If you're on the old Application Insights SDK (Microsoft.ApplicationInsights.AspNetCore), the migration is straightforward:

  1. Remove the old SDK packages.
  2. Add Azure.Monitor.OpenTelemetry.AspNetCore.
  3. Replace builder.Services.AddApplicationInsightsTelemetry() with the OpenTelemetry setup.
  4. Replace custom TelemetryInitializer classes with BaseProcessor<Activity> implementations.

Your telemetry data flows to the same Application Insights resource. Existing dashboards and alerts continue to work.

OpenTelemetry with Application Insights gives you vendor-neutral instrumentation with a powerful Azure-native backend. Instrument once, and you have traces, metrics, and logs correlated across your entire distributed system.