• Home
  • Extend
  • OpenTelemetry
  • Integration walk-throughs
  • Instrument your .NET application with OpenTelemetry

Instrument your .NET application with OpenTelemetry

This walkthrough shows how to add observability to your .NET application using the OpenTelemetry .NET libraries and tools.

FeatureSupported
Automatic InstrumentationYes
Automatic OneAgent IngestionYes

Prerequisites

  • Dynatrace version 1.254+
  • For tracing, W3C Trace Context is enabled
    1. From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
    2. Turn on Send W3C Trace Context HTTP headers.

Choose how to ingest data into Dynatrace

Auto-ingest for traces only

OneAgent currently only ingests traces automatically. If you are recording metrics or logs, choose the OTLP export route.

Apart from the following prerequisites, there are no other steps necessary when using OneAgent to ingest .NET OpenTelemetry data.

Prerequisites

  • OneAgent version 1.221+
  • Traces-only data
  • OpenTelemetry .NET Instrumentation agent support is enabled
    1. From the Dynatrace menu, go to Settings > Preferences > OneAgent features.
    2. Find and turn on OpenTelemetry (.NET).

Determine the API base URL

For details on how to assemble the base OTLP endpoint URL, see Export with OTLP. The URL should end in /api/v2/otlp.

Get API access token

The access token for ingesting traces, logs, and metrics can be generated in your Dynatrace menu under Access tokens.

Export with OTLP has more details on the format and the necessary access scopes.

Choose how you want to instrument your application

For .NET, OpenTelemetry supports automatic and manual instrumentation (or a combination of both).

Which instrumentation should I choose?

It's a good idea to start with automatic instrumentation and add manual instrumentation if the automatic approach doesn't work or doesn't provide enough information.

Automatically instrument your application optional

.NET automatic instrumentation can be configured either during development or later after deployment.

  1. Add the current version of OpenTelemetry.Extensions.Hosting as <PackageReference> to your .csproj project file.

    xml
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="[VERSION]" />
  2. Find the right instrumentation library for your .NET framework (the full list of currently supported libraries can be found here) and add its current version as <PackageReference> to your .csproj project file.

    xml
    <PackageReference Include="OpenTelemetry.Instrumentation.[FRAMEWORK_ID]" Version="[VERSION]" />
  1. Download the latest auto installer for the target operating system.
  2. Run (on Unix) or import (on Windows) the auto installer, to install and set up all necessary auto instrumentation libraries.
  3. Run your application.

Whether you configure automatic instrumentation during development or after deployment, you also need to configure the export parameters (for example, endpoint and protocol), unless you opt for OneAgent ingestion or configure them manually via manual instrumentation.

Manually instrument your application optional

Setup

  1. Add the current version ([VERSION]) of the following packages.

    xml
    <PackageReference Include="Microsoft.Extensions.Logging" Version="[VERSION]" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="[VERSION]" /> <PackageReference Include="OpenTelemetry" Version="[VERSION]" /> <PackageReference Include="OpenTelemetry.Api" Version="[VERSION]" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="[VERSION]" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs" Version="[VERSION]" />
  2. Add the following using statements to the startup class, which bootstraps your application.

    typescript
    using OpenTelemetry; using OpenTelemetry.Trace; using OpenTelemetry.Exporter; using OpenTelemetry.Metrics; using OpenTelemetry.Logs; using OpenTelemetry.Resources; using System.Diagnostics; using System.Diagnostics.Metrics; using Microsoft.Extensions.Logging;
  3. Add these fields to your startup class, with the first two indicating your Dynatrace URL and access token.

    typescript
    private static string DT_API_URL = ""; // TODO: Provide your SaaS/Managed URL here private static string DT_API_TOKEN = ""; // TODO: Provide the OpenTelemetry-scoped access token here private const string activitySource = "Dynatrace.DotNetApp.Sample"; // TODO: Provide a descriptive name for your application here public static readonly ActivitySource MyActivitySource = new ActivitySource(activitySource); private static LoggerFactory loggerFactoryOT;
    Value injection

    Instead of hardcoding the URL and token, you might also consider reading them from storage specific to your application framework (for example, environment variables or framework secrets).

  4. Add the initOpenTelemetry method to your startup class and invoke it as early as possible during your application startup. This initializes OpenTelemetry for the Dynatrace backend and creates default tracer and meter providers.

    csharp
    private static void initOpenTelemetry() { // ===== GENERAL SETUP ===== List<KeyValuePair<string, object>> dt_metadata = new List<KeyValuePair<string, object>>(); foreach (string name in new string[] { "dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties", "/var/lib/dynatrace/enrichment/dt_metadata.properties" }) { try { foreach (string line in System.IO.File.ReadAllLines(name.StartsWith("/var") ? name : System.IO.File.ReadAllText(name))) { var keyvalue = line.Split("="); dt_metadata.Add(new KeyValuePair<string, object>(keyvalue[0], keyvalue[1])); } } catch { } } Action<ResourceBuilder> configureResource = r => r .AddService(serviceName: "dotnet-quickstart") //TODO Replace with the name of your application .AddAttributes(dt_metadata); AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); // ===== TRACING SETUP ===== var tracerBuilder = Sdk.CreateTracerProviderBuilder() .ConfigureResource(configureResource) .SetSampler(new AlwaysOnSampler()) .AddOtlpExporter(otlpOptions => { otlpOptions.Endpoint = new Uri(DT_API_URL + "/v1/traces"); otlpOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; otlpOptions.Headers = $"Authorization=Api-Token {DT_API_TOKEN}"; otlpOptions.ExportProcessorType = ExportProcessorType.Batch; }) .AddSource(MyActivitySource.Name); // TODO // Add any additional automatic instrumentation libraries here // The format is Add[FRAMEWORK_NAME]Instrumentation // For example, for HttpClient: tracerBuilder.AddHttpClientInstrumentation(); tracerBuilder.Build(); // ===== METRIC SETUP ===== Sdk.CreateMeterProviderBuilder() .ConfigureResource(configureResource) .AddMeter("my_meter") .AddOtlpExporter((OtlpExporterOptions exporterOptions, MetricReaderOptions readerOptions) => { exporterOptions.Endpoint = new Uri(DT_API_URL + "/v1/metrics"); exporterOptions.Headers = $"Authorization=Api-Token {DT_API_TOKEN}"; exporterOptions.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; readerOptions.TemporalityPreference = MetricReaderTemporalityPreference.Delta; }) .Build(); // ===== LOG SETUP ===== var resourceBuilder = ResourceBuilder.CreateDefault(); configureResource!(resourceBuilder); loggerFactoryOT = LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { options.SetResourceBuilder(resourceBuilder).AddOtlpExporter(options => { options.Endpoint = new Uri(DT_API_URL + "/v1/logs"); options.Headers = $"Authorization=Api-Token {DT_API_TOKEN}"; options.ExportProcessorType = OpenTelemetry.ExportProcessorType.Batch; options.Protocol = OtlpExportProtocol.HttpProtobuf; }); }); }); }

Add tracing

Using MyActivitySource from the setup step, we can now start new activities (traces):

typescript
using var activity = MyActivitySource.StartActivity("Call to /myendpoint"); activity?.SetTag("http.method", "GET"); activity?.SetTag("net.protocol.version", "1.1");

In the above code, we:

  • Create a new activity (span) and name it "Call to /myendpoint"
  • Add two tags (attributes), following the semantic naming convention, specific to the action of this span: information on the HTTP method and version

The activity will be automatically set as the current and active span until the execution flow leaves the current method scope. Subsequent activities will automatically become child spans.

Collect metrics

  1. To instantiate new metric instruments, we first need a meter object.

    typescript
    Meter meter = new Meter("my-meter", "1.0.0"); //TODO Replace with the name of your meter
  2. With meter, we can now create individual instruments, such as a counter.

    typescript
    Counter<long> counter = meter.CreateCounter<long>("request_counter");
  3. We can now invoke the Add() method of counter to record new values with our counter and save additional attributes.

    typescript
    counter.Add(1, new("ip", "an ip address here"));

Connect logs

With the loggerFactoryOT variable, we initialized under Setup, we can now create individual logger instances, which will pass logged information straight to the configured OpenTelemetry endpoint at Dynatrace.

typescript
var logger = loggerFactoryOT.CreateLogger(); logger.LogInformation(eventId: 123, "Log line");

Ensure context propagation optional

Context propagation is particularly important when network calls (for example, REST) are involved.

If you are using automatic instrumentation and your networking libraries are covered by automatic instrumentation, this will be automatically taken care of by the instrumentation libraries. Otherwise, your code needs to take this into account.

Extracting the context when receiving a request

In the following example, we assume that we have received a network call via System.Web.HttpRequest and we define a CompositeTextMapPropagator instance to fetch the context information from the HTTP headers. We then pass that instance to Extract(), returning the context object, which allows us to continue the previous trace with our spans.

typescript
using OpenTelemetry.Context.Propagation; private CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator(), }); private static readonly Func<HttpRequest, string, IEnumerable<string>> valueGetter = (request, name) => request.Headers[name]; var parentContext = propagator.Extract(default, HttpContext.Request, valueGetter); using var activity = MyActivitySource.StartActivity("my-span", ActivityKind.Consumer, parentContext.ActivityContext);

Injecting the context when sending requests

In the following example, we send a REST request to another service and provide our existing context as part of the HTTP headers of our request.

To do so, we define a TextMapPropagator instance, which adds the respective information. Once we have instantiated our REST object, we pass it, along with the context and the setter instance, to Inject(), which will add the necessary headers to the request.

typescript
using OpenTelemetry.Context.Propagation; private CompositeTextMapPropagator propagator = new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), new BaggagePropagator() }); private static Action<HttpRequestMessage, string, string> _headerValueSetter => (request, name, value) => { request.Headers.Remove(name); request.Headers.Add(name, value); }; var request = new HttpRequestMessage(HttpMethod.Get, ""); propagator.Inject(new PropagationContext(activity!.Context, Baggage.Current), request, _headerValueSetter); private static readonly HttpClient client = new(); await client.SendAsync(request);

Configure data capture to meet privacy requirements optional

While Dynatrace automatically captures all OpenTelemetry resource and span attributes, only attribute values specified in the allowlist are stored and displayed in the Dynatrace web UI. This prevents accidental storage of personal data, so you can meet your privacy requirements and control the amount of monitoring data stored.

To view your custom span attributes, you need to allow them in the Dynatrace web UI first.

  • Span attributes: In the Dynatrace menu, go to Settings and select Server-side service monitoring > Span attributes.
  • Resource attributes: In the Dynatrace menu, go to Settings and select Server-side service monitoring > Resource attributes.

Verify data ingestion into Dynatrace

Once you have finished the instrumentation of your application, perform a couple of test actions to create and send demo traces, metrics, and logs and verify that they were correctly ingested into Dynatrace.

To do that for traces, in the Dynatrace menu, go to Distributed traces and select the Ingested traces tab. If you use OneAgent, select PurePaths instead.

Metrics and logs can be found under their respective entries at Observe and explore.

Related topics
  • Enrich ingested data with Dynatrace-specific dimensions

    Learn how to automatically enrich your telemetry data with Dynatrace-specific dimensions.