Collect Metrics for .NET Application with Open Telemetry and Prometheus

Ankita LunawatAnkita Lunawat
5 min read

Perquisites

  • AWS Account with Ubuntu 24.04 LTS EC2 Instance.

  • .NET SDK installed.

    Set Up Ubuntu EC2 Instance

Update the package list.

sudo apt update

Install the .NET SDK on your Ubuntu server, with below command.

sudo apt install -y dotnet-sdk-8.0

Setup Project Repository

For this example, we’ll use a sample .NET application from our GitHub repository.

Clone the below our Opentelemetry GitHub repository using following command.

git clone https://github.com/Ankita2295/opentelemetry-prometheus-for-dotnet-app.git

Navigate to the project directory.

cd opentelemetry-prometheus-for-dotnet-app

Instrumenting the Application with OpenTelemetry

Open the Program.cs file.

nano Program.cs

Add this line to the top of your Program.cs file.

using System.Globalization; and using Microsoft.AspNetCore.Mvc;

using System.Globalization;
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Mvc;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Instrumentation.Http;
using Prometheus;


var builder = WebApplication.CreateBuilder(args);

const string serviceName = "roll-dice";


// Add services to the container.
builder.Services.AddControllers();

// Create a custom meter for the API
var meter = new Meter("roll-dice.Metrics", "1.0");


var httpRequestCounter = meter.CreateCounter<long>("http_requests_total", description: "Total number of HTTP requests");


builder.Logging.AddOpenTelemetry(options =>
{
    options
        .SetResourceBuilder(
            ResourceBuilder.CreateDefault()
                .AddService(serviceName))
        .AddConsoleExporter();
});

// Configure OpenTelemetry
builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
    {
        tracerProviderBuilder
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("roll-dice"))
            .AddAspNetCoreInstrumentation() // Tracks incoming HTTP requests
            .AddHttpClientInstrumentation()
            .AddConsoleExporter(); // Optional: For debugging
    })
    .WithMetrics(meterProviderBuilder =>
    {
        meterProviderBuilder
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("roll-dice"))
            .AddAspNetCoreInstrumentation() // Tracks incoming HTTP request metrics
            .AddHttpClientInstrumentation()
            .AddPrometheusExporter(); // Expose metrics to Prometheus
    });

// Add middleware to count HTTP requests
var app = builder.Build();
// Use the Prometheus middleware to expose the /metrics endpoint.
app.UseRouting();
app.UseHttpMetrics(); // Middleware for collecting HTTP metrics
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapMetrics(); // Expose the /metrics endpoint for Prometheus scraping
});
string HandleRollDice([FromServices]ILogger<Program> logger, string? player)
{
    var result = RollDice();

    if (string.IsNullOrEmpty(player))
    {
        logger.LogInformation("Anonymous player is rolling the dice: {result}", result);
    }
    else
    {
        logger.LogInformation("{player} is rolling the dice: {result}", player, result);
    }

    return result.ToString(CultureInfo.InvariantCulture);
}

int RollDice()
{
    return Random.Shared.Next(1, 7);
}

app.MapGet("/rolldice/{player?}", HandleRollDice);

app.Run();
Explanation of the OpenTelemetry Instrumentation code for .Net Application

1. Imports

These lines import the needed namespaces, including those for OpenTelemetry, ASP.NET Core, metrics, and Prometheus. Each namespace offers classes and methods to help build the app's telemetry and HTTP features.

2. Creating the Web Application

WebApplication.CreateBuilder(args) starts the ASP.NET Core app builder. serviceName sets the service's name, which helps identify where traces and metrics come from in telemetry.

3. Adding Services to the Container

Adds support for controllers to the application, enabling the MVC framework so you can manage HTTP requests with controller methods.

4. Creating a Custom Meter for Metrics

Creates a custom Meter named roll-dice.Metrics to track metrics for this application.A counter metric called http_requests_total is set up to count the total number of HTTP requests.

5. Configuring OpenTelemetry for Logging

Configures OpenTelemetry to manage logging with the service name roll-dice. AddConsoleExporter() sets up console logging to show telemetry data, which is helpful for debugging.

6. Configuring OpenTelemetry for Tracing and Metrics

Configures OpenTelemetry for tracing and HTTP metrics:

  • Tracing: Tracks HTTP requests, monitors HTTP client interactions, and sends trace data to the console.

  • Metrics: Tracks HTTP request metrics and makes them available to Prometheus with the AddPrometheusExporter() method.

7. Building the Application and Configuring Middleware

app.UseRouting() sets up how requests are directed. app.UseHttpMetrics() adds Prometheus HTTP metrics, which automatically track details about incoming HTTP requests.

8. Exposing the /metrics Endpoint

Maps endpoints for the controllers. MapMetrics() makes the /metrics endpoint available, letting Prometheus collect metric data from the application.

9. Handling the /rolldice Endpoint

HandleRollDice method:

  • Manages requests to the /rolldice endpoint.

  • Uses the RollDice() method to roll a dice and logs the outcome.

  • Logs the player's name if given; if not, it logs that an anonymous player is rolling the dice.

RollDice method:

  • Creates a random number between 1 and 6, like rolling a dice.

app.MapGet("/rolldice/{player?}", HandleRollDice);: Connects the /rolldice endpoint to the HandleRollDice method, allowing the dice-rolling function to be used through HTTP requests.

10. Running the Application

Starts the web application and begins listening for incoming HTTP requests.

Next, open the MyPrometheusApp.csproj file.

vi MyPrometheusApp.csproj

Update the packages to the latest versions.

“OpenTelemetry.Instrumentation.AspNetCore” Version=”1.7.0″ and “OpenTelemetry.Instrumentation.Http” Version=”1.7.0″

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.6.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.7.0" />
    <PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>
Explanation of the code -

This project file configures the .NET web application to use modern tools for monitoring and web standards.

  1. Project Definition: This section specifies that the project uses the Microsoft.NET.Sdk.Web SDK, which is specifically designed for building web applications with ASP.NET Core, and it includes all the necessary tools and dependencies for web projects.

  2. The PropertyGroup section includes several key properties: TargetFramework, which specifies the .NET version the application targets, in this case, .NET 8.0, defining the runtime and libraries the application will use; Nullable, set to enable, which activates nullable reference types in C#, ensuring the code explicitly handles null values to reduce errors and increase safety; and ImplicitUsings, also set to enable, which automatically includes common using directives, eliminating the need to write them at the top of each file and simplifying the code by implicitly including commonly used namespaces.

  3. ItemGroup Section: This part lists all the external dependencies (NuGet packages) that the project needs.

Build and Run the Application

To compile and run your application, use the following commands to build the application.

dotnet build

Run the application.

dotnet run

This will start the application and expose the /metrics endpoint for Prometheus to scrape.

You can test your application by accessing the endpoint that returns the HTTP metrics, such as http://<EC2-Instance-IP>:8080/metrics.

http://<EC2-Instance-IP>:8080/metrics
0
Subscribe to my newsletter

Read articles from Ankita Lunawat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ankita Lunawat
Ankita Lunawat

I am a dedicated and experienced Cloud Engineer with two years in the industry, specializing in designing, implementing, and managing scalable and secure cloud infrastructures. With a strong foundation in AWS, Azure, and GCP, I excel at leveraging cloud services to optimize performance, enhance security, and reduce operational costs. My expertise includes automated deployment pipelines, infrastructure as code (IaC) with tools like Terraform and container orchestration using Kubernetes and Docker. Throughout my career, I've collaborated with cross-functional teams to deliver robust cloud solutions, ensuring high availability and fault tolerance. I'm passionate about staying at the forefront of cloud technology trends and continuously enhancing my skill set to provide innovative solutions that drive business success. Whether it's migrating legacy systems to the cloud or architecting new cloud-native applications, I bring a strategic approach to every project, focusing on efficiency, scalability, and reliability. In addition to my technical skills, I am an advocate for DevOps practices, promoting a culture of collaboration and continuous improvement within development and operations teams. My commitment to learning and adapting to new technologies ensures that I can meet the evolving needs of any organization and deliver top-tier cloud solutions.