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 automatic and manual instrumentation via an ASP.NET Core app.
First, install all the required packages:
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting --prerelease
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease
dotnet add package OpenTelemetry.Instrumentation.Http --prerelease
dotnet add package OpenTelemetry.Instrumentation.SqlClient --prerelease
Note that the --prerelease
flag is required for all instrumentation packages
because they are all are pre-release.
This will also install the OpenTelemetry
package.
Tracing
Next, add tracing via the System.Diagnostics
API.
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.AddOpenTelemetry().WithTracing(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:
View example output
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.
Metrics
Next, add metrics to the app. This will initialize a Meter to create a counter in code. It also configures automatic metrics instrumentation.
using System.Diagnostics;
using System.Diagnostics.Metrics;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
// Define some important constants to initialize tracing with
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var MyActivitySource = new ActivitySource(serviceName);
var builder = WebApplication.CreateBuilder(args);
var appResourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.AddConsoleExporter()
.AddSource(MyActivitySource.Name)
.SetResourceBuilder(appResourceBuilder)
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
.AddSqlClientInstrumentation();
});
var meter = new Meter(serviceName);
var counter = meter.CreateCounter<long>("app.request-counter");
builder.Services.AddOpenTelemetry().WithMetrics(metricProviderBuilder =>
{
metricProviderBuilder
.AddConsoleExporter()
.AddMeter(meter.Name)
.SetResourceBuilder(appResourceBuilder)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
var app = builder.Build();
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 });
// Up a counter for each request
counter.Add(1);
return "Hello, World!";
});
app.Run();
The output will be similar as with tracing, but now includes the request counter and periodically exports a histogram of request times for each route.
View example output
Activity.TraceId: bdf32b90913015106be97242fbfca649
Activity.SpanId: 7dba8699cd7ae27c
Activity.TraceFlags: Recorded
Activity.ParentSpanId: c28199417929ae65
Activity.ActivitySourceName: MyCompany.MyProduct.MyService
Activity.DisplayName: SayHello
Activity.Kind: Internal
Activity.StartTime: 2022-11-16T06:09:37.7932050Z
Activity.Duration: 00:00:00.0018200
Activity.Tags:
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: c1593668-d427-4e5d-a7a6-03c3dd4fa342
Activity.TraceId: bdf32b90913015106be97242fbfca649
Activity.SpanId: c28199417929ae65
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore
Activity.DisplayName: /hello
Activity.Kind: Server
Activity.StartTime: 2022-11-16T06:09:37.7878590Z
Activity.Duration: 00:00:00.0400410
Activity.Tags:
http.host: localhost:7026
http.method: GET
http.scheme: https
http.target: /hello
http.url: https://localhost:7026/hello
http.flavor: 1.1
http.user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
http.status_code: 200
Resource associated with Activity:
service.name: MyCompany.MyProduct.MyService
service.version: 1.0.0
service.instance.id: c1593668-d427-4e5d-a7a6-03c3dd4fa342
Export app.request-counter, Meter: MyCompany.MyProduct.MyService
(2022-11-16T06:09:04.2555860Z, 2022-11-16T06:09:44.2393710Z] LongSum
Value: 1
(2022-11-16T06:09:04.2574360Z, 2022-11-16T06:09:44.2393730Z] http.flavor: 1.1 http.host: localhost:7026 http.method: GET http.scheme: https http.status_code: 200 http.target: /hello Histogram
Value: Sum: 40.041 Count: 1
(-Infinity,0]:0
(0,5]:0
(0,10]:0
(0,25]:0
(0,50]:1
(0,75]:0
(0,100]:0
(0,250]:0
(0,500]:0
(0,750]:0
(0,1000]:0
(0,2500]:0
(0,5000]:0
(0,7500]:0
(0,10000]:0
(0,+Infinity]:0
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:
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 System.Diagnostics.Metrics;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
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 MyActivitySource = new ActivitySource(serviceName);
var builder = WebApplication.CreateBuilder(args);
var appResourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion);
// Configure to send data via the OTLP exporter.
// By default, it will send to port 4318, which the collector is listening on.
builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.AddOtlpExporter(opt =>
{
opt.Protocol = OtlpExportProtocol.HttpProtobuf;
})
.AddSource(MyActivitySource.Name)
.SetResourceBuilder(appResourceBuilder)
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
.AddSqlClientInstrumentation();
});
var meter = new Meter(serviceName);
var counter = meter.CreateCounter<long>("app.request-counter");
builder.Services.AddOpenTelemetry().WithMetrics(metricProviderBuilder =>
{
metricProviderBuilder
.AddOtlpExporter(opt =>
{
opt.Protocol = OtlpExportProtocol.HttpProtobuf;
})
.AddMeter(meter.Name)
.SetResourceBuilder(appResourceBuilder)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
var app = builder.Build();
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 });
// Up a counter for each request
counter.Add(1);
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.