OpenTelemetry Tracing API for .NET

OpenTelemetry-DotNet

OpenTelemetry-DotNetopen in new window is the .NET implementation of OpenTelemetry. It provides OpenTelemetry Tracing API which you can use to instrument your application with traces.

Activity API

OpenTelemetry API reuses .NET Activity APIopen in new window which existed long before OpenTelemetry was created. Activity API covers all OpenTelemetry requirements but uses slightly different API and terminology.

OpenTelemetry API.NET Activity API
TracerActivitySource
SpanActivity
NoopSpannull activity
SpanContextActivityContext
Span kindActivityKind
Attribute]Tag
SpanLinkActivityLink
SpanEventActivityEvent
span.IsRecording()activity.IsAllDataRequested

To install Activity API, add the following dependency to your project:

<ItemGroup>
  <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="5.0.1" />
</ItemGroup>

Quickstart

Step 1. Let's instrument the following function:

public string RedisGet(string key) {
    return this.redisDb.StringGet("mykey");
}

Step 2. Wrap the operation with a span:

using System.Diagnostics;

var activitySource = new ActivitySource("app_or_lib_name")

public string RedisGet(string key) {
    using var activity = this.activitySource.StartActivity("RedisGet");

    return this.redisDb.StringGet("mykey");
}
 

 


 



Step 3. Record errors and set status code:

public string RedisGet(string key) {
    using var activity = this.activitySource.StartActivity("RedisGet");

    try {
        return this.redisDb.StringGet("mykey");
    } catch (Exception ex) {
        activity?.RecordException(ex);
        activity?.SetStatus(Status.Error.WithDescription(ex.Message));
    }
}





 
 
 
 

Step 4. Record contextual information with attributes:

public string RedisGet(string key) {
    using var activity = this.activitySource.StartActivity("RedisGet");

    if (activity != null && activity.IsAllDataRequested) {
        activity.SetTag("redis.key", key);
    }

    try {
        return this.redisDb.StringGet("mykey");
    } catch (Exception ex) {
        activity?.RecordException(ex);
        activity?.SetStatus(Status.Error.WithDescription(ex.Message));
    }
}



 
 
 








And that's it! The operation is fully instrumented.

Tracer

To start creating spansopen in new window, you need a tracer (ActivitySource). You can create a tracer by providing the name and version of the library/application doing the instrumentation:

using System.Diagnostics;

static ActivitySource activitySource = new ActivitySource(
    "app_or_package_name",
    "semver1.0.0");

You can have as many tracers as you want, but usually you need only one tracer for an app or a library. Later, you can use tracer names to identify the instrumentation that produces the spans.

Creating spans

You can create spans using StartActivity. Because StartActivity returns null activity when OpenTelemetry is not enabled or span was not sampled, you need to check for null when working with activities:

var activity = activitySource.StartActivity("operation-name", ActivityKind.Server);

// do something

// End the span when the operation we are measuring is done.
activity?.Stop();

You can also create spans in using blocks to automatically stop the activity in the end of the block:

using (var activity = activitySource.StartActivity("operation-name")
{
    activity?.SetTag("http.method", "GET");
} // activity is automatically stopped during block disposal

Adding span attributes

To record contextual information, you can annotate spans with attributes. For example, an HTTP endpoint may have such attributes as http.method = GET and http.route = /projects/:id.

// To avoid expensive computations, check that span was sampled
// before setting any attributes.
if (activity != null && activity.IsAllDataRequested)
{
    activity.SetTag("http.method", "GET");
    activity.SetTag("http.route", "/projects/:id");
}

You can name attributes as you want, but for common operations you should use semantic attributesopen in new window convention.

Adding span events

You can annotate spans with events, for example, you can use events to record log messages:

activity?.AddEvent(
    new ActivityEvent(
        "log",
        DateTime.UtcNow,
        new ActivityTagsCollection(
            new Dictionary<string, object>
            {
                { "log.severity", "error" },
                { "log.message", "User not found" },
                { "enduser.id", 123 },
            }
        )
    )
);

Setting status code

You can set error status code to indicate that the operation contains an error:

catch (Exception ex)
{
    activity?.SetStatus(Status.Error.WithDescription(ex.Message));
}

Recording exceptions

OpenTelemetry provides a shortcut to record exceptionsopen in new window which is usually used together with SetStatus:

using (var activity = activitySource.StartActivity("operation-name"))
{
    try
    {
        Func();
    }
    catch (Exception ex)
    {
        activity?.RecordException(ex);
        activity?.SetStatus(Status.Error.WithDescription(ex.Message));
    }
}

What's next?