Getting Started

OpenTelemetry for .NET is unique among OpenTelemetry implementations, as it is integrated with the .NET System.Diagnostics library. At a high level, you can think of OpenTelemetry for .NET as a bridge between the telemetry available through System.Diagnostics and the greater OpenTelemetry ecosystem, such as OpenTelemetry Protocol (OTLP) and the OpenTelemetry Collector.

Installation

OpenTelemetry is available as a NuGet package. Install it with your preferred package manager client.

For example, using the .NET CLI:

dotnet add package OpenTelemetry

Console application

The following example demonstrates manual tracing via a console app.

First, install the required package:

dotnet add package OpenTelemetry.Exporter.Console

Next, paste the following code into your Program.cs file:

using System.Diagnostics;

using OpenTelemetry;
using OpenTelemetry.Trace;
using OpenTelemetry.Resources;

// Define some important constants to initialize tracing with
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";

// Configure important OpenTelemetry settings and the console exporter
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(serviceName)
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
    .AddConsoleExporter()
    .Build();

var MyActivitySource = new ActivitySource(serviceName);

using var activity = MyActivitySource.StartActivity("SayHello");
activity?.SetTag("foo", 1);
activity?.SetTag("bar", "Hello, World!");
activity?.SetTag("baz", new int[] { 1, 2, 3 });

The code will generate a single span like this:

Activity.Id:          00-cf0e89a41682d0cc7a132277da6a45d6-c714dd3b15e21378-01
Activity.ActivitySourceName: MyCompany.MyProduct.myService
Activity.DisplayName: SayHello
Activity.Kind:        Internal
Activity.StartTime:   2021-12-20T23:48:02.0467598Z
Activity.Duration:    00:00:00.0008508
Activity.TagObjects:
    foo: 1
    bar: Hello, World!
    baz: [1, 2, 3]
Resource associated with Activity:
    service.name: MyCompany.MyProduct.myService
    service.version: 1.0.0
    service.instance.id: 20c891c2-94b4-4203-a960-93a22e837a32

This output matches the span created in the preceding code sample.

ASP.NET Core

The following sample demonstrates tracing with ASP.NET Core.

First, install required packages:

dotnet add package OpenTelemetry.Extensions.Hosting --prerelease
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease
dotnet add package OpenTelemetry.Instrumentation.Http --prerelease
dotnet add package OpenTelemetry.Instrumentation.SqlClient --prerelease

Next, paste the following code into your Program.cs file:

using System.Diagnostics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

// Define some important constants to initialize tracing with
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);

// Configure important OpenTelemetry settings, the console exporter, and instrumentation library
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
{
    tracerProviderBuilder
    .AddConsoleExporter()
    .AddSource(serviceName)
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
    .AddHttpClientInstrumentation()
    .AddAspNetCoreInstrumentation()
    .AddSqlClientInstrumentation();
});

var app = builder.Build();

var MyActivitySource = new ActivitySource(serviceName);

app.MapGet("/hello", () =>
{
    // Track work inside of the request
    using var activity = MyActivitySource.StartActivity("SayHello");
    activity?.SetTag("foo", 1);
    activity?.SetTag("bar", "Hello, World!");
    activity?.SetTag("baz", new int[] { 1, 2, 3 });

    return "Hello, World!";
});

app.Run();

When you run the app and navigate to the /hello route, you’ll see output about spans similar to the following:

Activity.Id:          00-d72f7e51dd06b57211f415489df89b1c-c8a394817946316d-01
Activity.ParentId:    00-d72f7e51dd06b57211f415489df89b1c-e1c9fde6c8f415ad-01
Activity.ActivitySourceName: MyCompany.MyProduct.MyServiceActivity.DisplayName: SayHello
Activity.Kind:        Internal
Activity.StartTime:   2021-12-21T01:15:27.5712866Z
Activity.Duration:    00:00:00.0000487
Activity.TagObjects:
    foo: 1
    bar: Hello, World!
    baz: [1, 2, 3]
Resource associated with Activity:
    service.name: MyCompany.MyProduct.MyService
    service.version: 1.0.0
    service.instance.id: 45aacfb0-e117-40cb-9d4d-9bcca661f6dd

Activity.Id:          00-d72f7e51dd06b57211f415489df89b1c-e1c9fde6c8f415ad-01
Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore
Activity.DisplayName: /hello
Activity.Kind:        Server
Activity.StartTime:   2021-12-21T01:15:27.5384997Z
Activity.Duration:    00:00:00.0429197
Activity.TagObjects:
    http.host: localhost:7207
    http.method: GET
    http.target: /hello
    http.url: https://localhost:7207/hello
    http.user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
    http.status_code: 200
    otel.status_code: UNSET
Resource associated with Activity:
    service.name: MyCompany.MyProduct.MyService
    service.version: 1.0.0
    service.instance.id: 45aacfb0-e117-40cb-9d4d-9bcca661f6dd

This output has both the manually created span to track work in the route, and a span created by the OpenTelemetry.Instrumentation.AspNetCore instrumentation library that tracks the inbound ASP.NET Core request.

Send traces to a collector

The OpenTelemetry Collector is a vital component of most production deployments. A collector is most beneficial in the following situations, among others:

  • A single telemetry sink shared by multiple services, to reduce overhead of switching exporters
  • Aggregate traces across multiple services, running on multiple hosts
  • A central place to process traces prior to exporting them to a backend

Configure and run a local collector

First, save the following collector configuration code to a file in the /tmp/ directory:

# /tmp/otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
exporters:
  logging:
    loglevel: debug
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging]
      processors: [batch]

Then run the docker command to acquire and run the collector based on this configuration:

docker run -p 4318:4318 \
    -v /tmp/otel-collector-config.yaml:/etc/otel-collector-config.yaml \
    otel/opentelemetry-collector:latest \
    --config=/etc/otel-collector-config.yaml

You will now have an collector instance running locally.

Modify the code to export spans via OTLP

The next step is to modify the code to send spans to the collector via OTLP instead of the console.

First, add the following package:

dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

Next, using the ASP.NET Core code from earlier, replace the console exporter with an OTLP exporter:

using System.Diagnostics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

// Define some important constants to initialize tracing with
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);

// Configure to send data via the OTLP exporter.
// By default, it will send to port 4318, which the collector is listening on.
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
{
    tracerProviderBuilder
    .AddOtlpExporter(opt =>
    {
        opt.Protocol = OtlpExportProtocol.HttpProtobuf;
    })
    .AddSource(serviceName)
    .SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService(serviceName: serviceName, serviceVersion: serviceVersion))
    .AddHttpClientInstrumentation()
    .AddAspNetCoreInstrumentation()
    .AddSqlClientInstrumentation();
});

var app = builder.Build();

var MyActivitySource = new ActivitySource(serviceName);

app.MapGet("/hello", () =>
{
    // Track work inside of the request
    using var activity = MyActivitySource.StartActivity("SayHello");
    activity?.SetTag("foo", 1);
    activity?.SetTag("bar", "Hello, World!");
    activity?.SetTag("baz", new int[] { 1, 2, 3 });

    return "Hello, World!";
});

app.Run();

By default, it will send spans to localhost:4318, which is what the collector is listening on.

Run the application

Run the application like before:

dotnet run

Now, telemetry will be output by the collector process.

Next steps

To ensure you’re getting the most data as easily as possible, install instrumentation libraries to generate observability data.

Additionally, enriching your codebase with manual instrumentation gives you customized observability data.

You’ll also want to configure an appropriate exporter to export your telemetry data to one or more telemetry backends.

You can also check the automatic instrumentation for .NET, which is currently in beta.