Skip to main content

Command Palette

Search for a command to run...

OpenTelemetry vs Application Insights in .NET - What Should You Use?

Updated
11 min read
OpenTelemetry vs Application Insights in .NET - What Should You Use?
P
Senior Software Engineer specialising in cloud architecture, distributed systems, and modern .NET development, with over two decades of experience designing and delivering enterprise platforms in financial, insurance, and high-scale commercial environments. My focus is on building systems that are reliable, scalable, and maintainable over the long term. I’ve led modernisation initiatives moving legacy platforms to cloud-native Azure architectures, designed high-throughput streaming solutions to eliminate performance bottlenecks, and implemented secure microservices environments using container-based deployment models and event-driven integration patterns. From an architecture perspective, I have strong practical experience applying approaches such as Vertical Slice Architecture, Domain-Driven Design, Clean Architecture, and Hexagonal Architecture. I’m particularly interested in modular system design that balances delivery speed with long-term sustainability, and I enjoy solving complex problems involving distributed workflows, performance optimisation, and system reliability. I enjoy mentoring engineers, contributing to architectural decisions, and helping teams simplify complex systems into clear, maintainable designs. I’m always open to connecting with other engineers, architects, and technology leaders working on modern cloud and distributed system challenges.

When observability comes up you might ask whether you should use OpenTelemetry or Application Insights. That sounds reasonable, but it puts two different things in the same box. OpenTelemetry is a standard way to collect, describe and export telemetry. Application Insights is an Azure observability product where that telemetry can be stored, queried and analysed. So the better question is not whether you should use OpenTelemetry or Application Insights. The better question is this, What should instrument my .NET application, and where should I send the data?

For many modern .NET systems on Azure, the answer is simple. Use OpenTelemetry for instrumentation and send the data to Azure Monitor Application Insights. That gives you standardised telemetry without giving up the Azure-native monitoring experience you probably already use.

The old Application Insights model

For years, many .NET applications used the Application Insights SDK directly. You added the package, configured the connection string, deployed the app and started seeing requests, dependencies, exceptions and traces in the Azure portal. For a lot of people, that was enough. It still can be enough for small systems. The benefit was speed. You could get useful telemetry quickly without designing a full observability strategy. ASP.NET Core request tracking worked. HTTP dependency tracking worked. Exceptions appeared in the portal. You could use Application Map. You could write KQL queries. You could set alerts. That is still valuable, the problem was coupling. Your application instrumentation was strongly tied to Application Insights. If you later wanted to send traces to another backend, or you wanted the same telemetry model across services that did not all live in Azure, things became less clean. You could still make it work, but the instrumentation story was not as portable as it should have been.

That is where OpenTelemetry matters.

What OpenTelemetry changes

OpenTelemetry gives you a standard way to collect telemetry from applications. In .NET, this fits naturally because the platform already has observability primitives. You use ILogger for logs, Meter for metrics, and ActivitySource with Activity for distributed tracing. OpenTelemetry can collect from those platform APIs and export the data to different observability backends. That means your application does not need to care whether the final destination is Application Insights, Grafana, Jaeger, Prometheus, Datadog, Honeycomb, New Relic or another tool.

OpenTelemetry is not just another monitoring library. It is a way to stop your application code being hard-wired to one vendor. Thats important more as systems grow. A single ASP.NET Core API can get away with a very simple setup. A larger estate with APIs, workers, Azure Functions, background services, queues, database calls, HTTP calls and external integrations needs something more consistent. You want traces to flow across service boundaries. You want logs to carry the right context. You want metrics that tell you how the system behaves, not just whether the process is alive. You want a consistent model regardless of where each service runs. OpenTelemetry helps with that.

Application Insights is not dead

Some developers hear OpenTelemetry and assume Application Insights is being replaced. Thats the wrong conclusion. Application Insights is still useful. It gives you a practical Azure-native place to inspect telemetry, query logs, diagnose failures, view dependencies, build dashboards and configure alerts. If your workloads run mostly on Azure, Application Insights is often still the best default backend.

The change is where you should put the boundary. Your application code should be instrumented using standard .NET and OpenTelemetry patterns. Application Insights should be treated as one possible destination for that telemetry. It means you can use Application Insights today without making the application code depend on Application Insights forever.

The modern default for .NET on Azure

For an ASP.NET Core application running on Azure, the current practical default is to use the Azure Monitor OpenTelemetry Distro. This sends telemetry to Azure Monitor following the OpenTelemetry specification. Microsoft documents the simple setup using AddOpenTelemetry().UseAzureMonitor() with the Application Insights connection string supplied through configuration.

A minimal setup:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddOpenTelemetry()
    .UseAzureMonitor();

var app = builder.Build();

app.MapGet("/orders/{id:guid}", (Guid id, ILogger<Program> logger) =>
{
    logger.LogInformation("Reading order {OrderId}", id);

    return Results.Ok(new
    {
        OrderId = id,
        Status = "Processing"
    });
});

app.Run();

In Azure, you would usually provide the connection string using an environment variable:

APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=...;IngestionEndpoint=...

That gives you a clean separation.

Your application uses normal .NET logging and tracing APIs. OpenTelemetry collects the telemetry. Azure Monitor receives it. Application Insights gives you the diagnostics experience.

Thats a better long-term shape than spreading Application Insights-specific code everywhere.

What about logs?

Logs are still useful, but logs are not the whole observability story. A common mistake is to treat observability as a better logging setup. That is too narrow. Logs tell you what happened at a point in time. Traces show how a request moved through the system. Metrics show how the system behaves over time. A useful .NET observability setup needs all three. The code should still use ILogger, but the log messages should be structured. This means you should avoid building strings manually and pass properties as named values instead.

This is useful:

logger.LogInformation(
    "Payment {PaymentId} for batch {BatchId} completed with status {Status}",
    paymentId,
    batchId,
    status);

This is less useful:

logger.LogInformation(
    $"Payment {paymentId} for batch {batchId} completed with status {status}");

The first version gives your backend named fields to query. The second version gives you a formatted string. That difference matters when production is broken and you need to search for one payment, one customer, one batch or one correlation ID.

What about traces?

Distributed tracing is where OpenTelemetry becomes especially valuable. Imagine an order request enters an ASP.NET Core API. The API writes to SQL Server, publishes a message and calls another service over HTTP. That second service writes to Cosmos DB and calls a payment provider. If each component logs separately, you can still diagnose problems, but you must stitch the story together yourself.

With distributed tracing, the request has a trace context. Each operation becomes part of the same trace. You can see the path through the system and identify where time was spent or where the failure occurred.

In .NET, custom tracing should normally use ActivitySource.

using System.Diagnostics;

public sealed class OrderPricingService
{
    private static readonly ActivitySource ActivitySource = new("DotNetDigest.Orders");

    public async Task<decimal> CalculatePriceAsync(Guid orderId, CancellationToken stopToken)
    {
        using var activity = ActivitySource.StartActivity("Calculate order price");

        activity?.SetTag("order.id", orderId);

        await Task.Delay(50, stopToken);

        var price = 129.99m;

        activity?.SetTag("order.price", price);

        return price;
    }
}

The important point is not the exact class name. The important point is that your application creates meaningful spans around business operations, not just framework operations. Automatic instrumentation can tell you that an HTTP call happened. It cannot always tell you why that call mattered. That is where custom spans help.

What about metrics?

Metrics are often underused in .NET applications. Logs and traces are good for diagnosis. Metrics are better for operational signals. You should not need to read logs to know whether a payment processor is falling behind. You should have metrics for queue depth, processing duration, failure rate, retry count and throughput.

In .NET, you can use Meter to define application metrics.

using System.Diagnostics.Metrics;

public sealed class PaymentMetrics
{
    private static readonly Meter Meter = new("DotNetDigest.Payments");

    private readonly Counter<int> _paymentsProcessed =
        Meter.CreateCounter<int>("payments.processed");

    private readonly Counter<int> _paymentsFailed =
        Meter.CreateCounter<int>("payments.failed");

    public void PaymentProcessed(string provider)
    {
        _paymentsProcessed.Add(1, new KeyValuePair<string, object?>("provider", provider));
    }

    public void PaymentFailed(string provider, string reason)
    {
        _paymentsFailed.Add(
            1,
            new KeyValuePair<string, object?>("provider", provider),
            new KeyValuePair<string, object?>("reason", reason));
    }
}

Metrics need discipline. Do not attach high-cardinality values such as user IDs, order IDs or payment IDs as metric dimensions. That can create expensive and messy telemetry. Use metrics for aggregate behaviour. Use logs and traces for specific cases.

The cost problem nobody wants to discuss

Observability is not free. That doesnt mean you should avoid it. It means you should design it properly. A noisy system can generate a large amount of telemetry. Every request, dependency call, trace, exception and log line can become data that needs to be ingested, stored and queried. At small scale, this may not matter. At production scale, it can become a real cost. This is where sampling matters. Sampling reduces the volume of telemetry while keeping enough data to diagnose the system. Microsoft’s documentation for Application Insights with OpenTelemetry says sampling is used to reduce telemetry volume, control costs and avoid throttling. It also notes that sampling is not enabled by default in Application Insights OpenTelemetry distros and must be explicitly configured. Dont assume sampling is already protecting you. Check your configuration. A mature observability setup is not the one that collects everything forever. It is the one that collects enough useful data to support diagnosis, operations and audit needs without creating noise or waste.

A sensible production setup

For a serious .NET application on Azure, I would start with this shape.

Use OpenTelemetry as the instrumentation path. Use standard .NET APIs for logs, metrics and traces. Send telemetry to Azure Monitor Application Insights using the Azure Monitor OpenTelemetry Distro. Configure sampling deliberately. Use structured logging. Add custom spans around business operations. Add metrics for throughput, failure rates and processing delays. Keep correlation IDs visible at service boundaries. That gives you a setup that is practical today and still flexible later. The key is to avoid two extremes. The first extreme is doing almost nothing and hoping logs are enough. That usually fails when the first serious production incident happens.

The second extreme is overengineering observability into a platform project before the application has basic useful signals. That creates diagrams, packages and dashboards, but not necessarily better diagnosis. Start with useful telemetry. Then improve it.

When Application Insights alone is enough

There are still cases where direct Application Insights usage may be enough. If you have a small internal application, a simple Azure-hosted API or a system with low complexity, you may not need a large observability setup. You might choose the simplest Application Insights integration and move on. Thats not wrong. Engineering maturity does not mean always choosing the most portable architecture. It means choosing the right level of design for the system you actually have. The risk is when a simple setup becomes the default for everything, including systems that are no longer simple. Once you have multiple services, background workers, queues, eventing, retries and third-party dependencies, the need for consistent tracing and metrics becomes harder to ignore.

When OpenTelemetry matters more

OpenTelemetry matters when your system needs portability, consistency or a cleaner long-term boundary. It becomes important when you have services running in different places. Again when you want the option to change observability backends. And again when different teams use different languages or when traces need to cross APIs, workers and message handlers.

For a senior .NET team, this is usually the strongest argument.

OpenTelemetry gives you a standard. Application Insights gives you a backend. You can use both without confusing their roles.

What about Azure Functions?

Azure Functions needs separate attention because the host and the worker both produce telemetry. Microsoft documents OpenTelemetry support for Azure Functions, including exporting logs and traces in an OpenTelemetry format. For new and existing Function Apps, Microsoft also recommends using the Azure Monitor OpenTelemetry Exporter to send telemetry to Application Insights. A lot of .NET systems use a mix of ASP.NET Core APIs and Azure Functions. You do not want one observability model for the API and a completely different model for the functions. You want a request or event to be traceable across the whole flow.

For example, an HTTP request might start an orchestration, write to a queue, trigger a Function, call an external API and update a database. The observability goal is not just to know that each individual component ran. The goal is to see the end-to-end path.

The real decision

So what should you actually use? For most teams on Azure, I would not frame this as OpenTelemetry versus Application Insights. I would frame it like this, Use OpenTelemetry as the instrumentation standard. Use Application Insights as the Azure-native analysis and diagnostics backend. Use structured logs, distributed traces and metrics together. Configure sampling before telemetry volume becomes a cost problem. Thats the balanced approach.

It gives you the Azure experience today and keeps your options open for growth.

If youre building a new .NET application on Azure in 2026, start with OpenTelemetry and export to Azure Monitor Application Insights. Dont scatter Application Insights-specific code through your application. Use the normal .NET observability APIs. Let OpenTelemetry collect the data. Let Azure Monitor and Application Insights give you the operational view. Thats not the most complicated setup. It is the cleanest default. And it answers the original question properly.

You should not choose between OpenTelemetry and Application Insights as if they are competitors. Use OpenTelemetry to describe and export the telemetry. Use Application Insights to understand what your system is doing in production.

https://learn.microsoft.com/en-us/dotnet/api/overview/azure/monitor.opentelemetry.aspnetcore-readme?view=azure-dotnet

https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel

https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-sampling

https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto

https://learn.microsoft.com/en-us/azure/azure-functions/functions-monitoring