Menu
OpenTelemetry Instrumentation .NET Manual instrumentation of .NET applications
Open source

Manual instrumentation of .NET applications with OpenTelemetry

This guide will walk you using the OpenTelemetry SDK to collect metrics and traces from a simple ASP.NET Core application in .NET 6.

Prerequisites

To follow this guide, you will need to have Visual Studio Code or Visual Studio 2022 installed and the .NET 6 SDK.

If these haven’t been installed yet, choose an IDE and install the most recent stable SDK version:

Scaffold New Project

First, we will create a new ASP.NET Core project.

Run the following in a terminal:

shell
dotnet new web -o HelloWorldApi

Or using Visual Studio 2022, create a new project and select the “ASP.NET Core Empty” template.

Open the new project - the Program.cs file will look like this:

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Now we have a minimal ASP.NET Core API setup and ready to instrument using the OpenTelemetry SDK.

OpenTelemetry SDK Installation

For this example, we’ll be using the following packages:

  • OpenTelemetry.Extensions.Hosting
    • Adds OpenTelemetry hooks to dependency injection
  • OpenTelemetry.Instrumentation.AspNetCore
    • Collects metrics and traces from ASP.NET Core
  • OpenTelemetry.Instrumentation.Runtime
    • .NET runtime metrics, like garbage collection statistics
  • OpenTelemetry.Exporter.Console
    • Outputs collected metrics and traces to stdout

Run the following to install the four packages:

shell
dotnet add package OpenTelemetry.Extensions.Hosting --version 1.5.1
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --version 1.5.0-beta.1
dotnet add package OpenTelemetry.Instrumentation.Runtime --version 1.5.0
dotnet add package OpenTelemetry.Exporter.Console --version 1.5.1

If installing using Visual Studio 2022, be sure to select “Include prelease” in the NuGet Package Manager.

OpenTelemetry Tracing Setup

To setup tracing, modify Program.cs to look like:

csharp
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService(serviceName: builder.Environment.ApplicationName))
    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation()
        .AddConsoleExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

These additions:

  1. Configure the service name, stored as service.name on traces
  2. Enable trace collection from ASP.NET Core
  3. Output any traces collected to stdout

Run the application via your IDE or this terminal command:

shell
dotnet run

Open a browser or curl the application and you will see trace data written to the terminal.

OpenTelemetry Metrics Setup

To collect metrics in addition to traces, once more modify Program.cs. This time it should look like:

csharp
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService(serviceName: builder.Environment.ApplicationName))
    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation()
        .AddConsoleExporter())
    .WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddConsoleExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Notice this looks very similar to enabling collection of traces. The new lines:

  1. Enable metrics collection from ASP.NET Core
  2. Enable metrics collection from the .NET runtime
  3. Output any metrics collected to stdout

Run the application again - this time metrics and traces will be written to the terminal.

OpenTelemetry resource attributes are added to logs, traces, and metrics emitted by your application. These are standardized attributes that makes telemetry data much easier.

Our sample already sets the service.name attribute in the ConfigureResource call. We’ll be adding four more recommended attributes. Read more about OpenTelemetry resource attributes.

After modifying our ConfigureResource call to set the additional attributes, Program.cs now looks like:

csharp
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService(
        serviceNamespace: "demo-namespace",
        serviceName: builder.Environment.ApplicationName,
        serviceVersion: Assembly.GetEntryAssembly()?.GetName().Version?.ToString(),
        serviceInstanceId: Environment.MachineName
    ).AddAttributes(new Dictionary<string, object>
    {
        { "deployment.environment", builder.Environment.EnvironmentName }
    }))
    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation()
        .AddConsoleExporter())
    .WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddConsoleExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Now if you run the application, these new attributes will be included with metric and trace data written to the terminal.

Sending OpenTelemetry Data to Grafana

Now we’ve seen metrics and traces written to stdout, let’s extend the application to send this telemetry data to Grafana.

Note: If you use Grafana Cloud, follow the OpenTelemetry Integration, which creates a Grafana Agent configuration for you.

Before changing any code, follow the steps in this article to setup the Grafana Agent:

Once the Agent is setup locally and listening for OpenTelemetry data, we will install a new exporter.

Once more, run this command in your terminal is use the NuGet Packager Manager in Visual Studio:

shell
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.5.1

The only change needed to add a call to AddOtlpExporter after each call AddConsoleExporter, resulting in this Program.cs:

csharp
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService(
        serviceNamespace: "demo-namespace",
        serviceName: builder.Environment.ApplicationName,
        serviceVersion: Assembly.GetEntryAssembly()?.GetName().Version?.ToString(),
        serviceInstanceId: Environment.MachineName
    ).AddAttributes(new Dictionary<string, object>
    {
        { "deployment.environment", builder.Environment.EnvironmentName }
    }))
    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation()
        .AddConsoleExporter()
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddConsoleExporter()
        .AddOtlpExporter());

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Note: In a production scenario, you’d remove the call to AddConsoleExporter. We’re leaving it here for demo purposes.

The Grafana Agent is already setup to listen on the default ports the OTLP (OpenTelemetry Protocol) exporter connects over - no additional configuration is required.

If the Agent is not running locally with the default gRPC endpoint (localhost:4317), you may change the OTLP endpoint by specificing a hostname and port like so:

csharp
.AddOtlpExporter(config => config.Endpoint = new Uri("http://host:port"))

Now when you run this latest version, you should still see metrics and traces written to stdout. You will see also see new metrics and traces written to the Grafana instance the Agent has been configured to write to.