跳转至

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.

ASP.NET Core

The following example demonstrates using Instrumentation Libraries and manual instrumentation via an ASP.NET Core app.

First, create your basic ASP.NET Core site:

dotnet new mvc

Next, Add the Core OpenTelemetry packages

dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting

Now let's add the instrumentation packages for ASP.NET Core. This will give us some automatic spans for each HTTP request to our app.

dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease

Note that as the Semantic Conventions for attribute names are not currently stable the instrumentation package is currently not in a released state. That doesn't mean that the functionality itself is not stable. This means that you need to use the --prerelease flag, or install a specific version of the package

Setup

Next, we need to add OpenTelemetry to our Service Collection in Program.cs so that its listening correctly.

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

var builder = WebApplication.CreateBuilder(args);

// .. other setup

builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
        tracerProviderBuilder
            .AddSource(DiagnosticsConfig.ActivitySource.Name)
            .ConfigureResource(resource => resource
                .AddService(DiagnosticsConfig.ServiceName))
            .AddAspNetCoreInstrumentation()
            .AddConsoleExporter());

// ... other setup

public static class DiagnosticsConfig
{
    public const string ServiceName = "MyService";
    public static ActivitySource ActivitySource = new ActivitySource(ServiceName);
}

At this stage, you should be able to run your site, and see a Console output similar to this:

Note: an Activity in .NET is analogous to a Span in OpenTelemetry terminology

View example output
Activity.TraceId:            54d084eba205a7a39398df4642be8f4a
Activity.SpanId:             aca5e39a86a17d59
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName:        /
Activity.Kind:               Server
Activity.StartTime:          2023-02-21T12:19:28.2499974Z
Activity.Duration:           00:00:00.3106744
Activity.Tags:
    net.host.name: localhost
    net.host.port: 5123
    http.method: GET
    http.scheme: http
    http.target: /
    http.url: http://localhost:5123/
    http.flavor: 1.1
    http.user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50
    http.status_code: 200
Resource associated with Activity:
    service.name: MyService
    service.instance.id: 2c7ca153-e460-4643-b550-7c08487a4c0c

Manual Instrumentation

Next, add tracing via the System.Diagnostics API.

Paste the following code into your HomeController's Index action:

public IActionResult Index()
{
    // Track work inside of the request
    using var activity = DiagnosticsConfig.ActivitySource.StartActivity("SayHello");
    activity?.SetTag("foo", 1);
    activity?.SetTag("bar", "Hello, World!");
    activity?.SetTag("baz", new int[] { 1, 2, 3 });

    return View();
}

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

View example output
Activity.TraceId:            47d25efc8b5e9184ce57e692f5f65465
Activity.SpanId:             bb864adcf4592f54
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       acbff23f5ad721ff
Activity.ActivitySourceName: MyService
Activity.DisplayName:        SayHello
Activity.Kind:               Internal
Activity.StartTime:          2023-02-21T12:27:41.9596458Z
Activity.Duration:           00:00:00.0005683
Activity.Tags:
    foo: 1
    bar: Hello, World!
    baz: [1,2,3]
Resource associated with Activity:
    service.name: MyService
    service.instance.id: 2b07a9ca-29c4-4e01-b0ed-929184b32192

You'll notice the Activity objects from ASP.NET Core alongside the Activity we created manually in our controller action.

Metrics

Next we'll add the ASP.NET Core automatically generated metrics to the app.

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

var builder = WebApplication.CreateBuilder(args);

// .. other setup

builder.Services.AddOpenTelemetry()
    .WithTracing(/*  .. tracing setup */ )
    .WithMetrics(metricsProviderBuilder =>
        metricsProviderBuilder
            .ConfigureResource(resource => resource
                .AddService(DiagnosticsConfig.ServiceName))
            .AddAspNetCoreInstrumentation()
            .AddConsoleExporter());

// .. other setup

If you run your application now, you'll see a series of metrics output to the console. like this.

View example output
Export http.server.duration, Measures the duration of inbound HTTP requests., Unit: ms, Meter: OpenTelemetry.Instrumentation.AspNetCore/1.0.0.0
(2023-02-21T12:38:57.0187781Z, 2023-02-21T12:44:16.9651349Z] http.flavor: 1.1 http.method: GET http.route: {controller=Home}/{action=Index}/{id?} http.scheme: http http.status_code: 200 net.host.name: localhost net.host.port: 5123 Histogram
Value: Sum: 373.4504 Count: 1 Min: 373.4504 Max: 373.4504
(-Infinity,0]:0
(0,5]:0
(5,10]:0
(10,25]:0
(25,50]:0
(50,75]:0
(75,100]:0
(100,250]:0
(250,500]:1
(500,750]:0
(750,1000]:0
(1000,2500]:0
(2500,5000]:0
(5000,7500]:0
(7500,10000]:0
(10000,+Infinity]:0

Manual Metrics

Next, add some manual metrics to the app. This will initialize a Meter to create a counter in code.

var builder = WebApplication.CreateBuilder(args);

// .. other setup

builder.Services.AddOpenTelemetry()
    .WithTracing(/*  .. tracing setup */ )
    .WithMetrics(metricsProviderBuilder =>
        metricsProviderBuilder
            .AddMeter(DiagnosticsConfig.Meter.Name)
            // .. more metrics
             );

public static class DiagnosticsConfig
{
    public const string ServiceName = "MyService";

    // .. other config

    public static Meter Meter = new(ServiceName);
    public static Counter<long> RequestCounter =
        Meter.CreateCounter<long>("app.request_counter");
}

Now we can increment the counter in our Index action.

    public IActionResult Index()
    {
        // do other stuff

        DiagnosticsConfig.RequestCounter.Add(1,
            new("Action", nameof(Index)),
            new("Controller", nameof(HomeController)));

        return View();
    }

You'll notice here that we're also adding Tags (OpenTelemetry Attributes) to our request counter that distinguishes it from other requests. You should now see an output like this.

View example output
1
2
3
Export app.request_counter, Meter: MyService
(2023-02-21T13:11:28.7265324Z, 2023-02-21T13:11:48.7074259Z] Action: Index Controller: HomeController LongSum
Value: 1

Tip: if you comment out the .AddAspNetCoreInstrumentation() line in Program.cs you'll be able to see the output better.

Send data 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:
      grpc:
exporters:
  logging:
    loglevel: debug
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging]
      processors: [batch]
    metrics:
      receivers: [otlp]
      exporters: [logging]
      processors: [batch]

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

1
2
3
4
docker run -p 4317:4317 \
    -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:

1
2
3
4
5
6
7
8
9
builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
        tracerProviderBuilder
             // .. other config
            .AddOtlpExporter())
    .WithMetrics(metricsProviderBuilder =>
        metricsProviderBuilder
            // .. other config
            .AddOtlpExporter());

By default, it will send spans to localhost:4317, which is what the collector is listening on if you've followed the step above.

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.