Configurable OpenTelemetry setup for .NET applications providing tracing, metrics, and logging via OTLP, configurable through code or appsettings.json.
- One-call setup β tracing, metrics and logging via a single
AddTelemetry(), configured fromappsettings.jsonor code - All three signals over OTLP β HTTP/protobuf or gRPC, to any OTLP-compatible backend
- Built-in instrumentation β ASP.NET Core,
HttpClient, SQL Client and .NET runtime metrics, each toggleable - Sensible defaults β sampling, health-check path exclusion and exception recording work out of the box
- Startup validation β misconfiguration fails fast with a clear error
- Extensible β
ConfigureTracing/ConfigureMetrics/ConfigureLogginghooks for custom sources, meters and providers - Broad target support β
netstandard2.0andnet10.0
dotnet add package OpenTelemetryExtension.Configurationbuilder.Services.AddTelemetry(builder.Configuration);{
"Telemetry": {
"Enabled": true,
"Endpoint": "http://localhost:4318",
"ServiceName": "my-api"
}
}That's it β tracing, metrics and logging are exported via OTLP.
All options live under the Telemetry key in appsettings.json.
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
bool |
false |
Must be true to activate telemetry. |
Endpoint |
Uri |
(required) | OTLP collector endpoint, e.g. http://localhost:4318. |
Headers |
string |
"" |
Exporter headers. Format: key1=value1,key2=value2. |
Protocol |
string |
HttpProtobuf |
HttpProtobuf (port 4318) or Grpc (port 4317). |
ServiceName |
string? |
null |
Service name shown in the backend. |
ResourceAttributes |
object |
{} |
Extra resource attributes, e.g. { "deployment.environment": "production", "team": "backend" }. |
SampleRatio |
double |
1.0 |
Fraction of traces to sample. 0.1 = 10%, 1.0 = all. |
EnableTracing |
bool |
true |
Enables distributed tracing. |
EnableMetrics |
bool |
true |
Enables metrics collection. |
EnableLogging |
bool |
true |
Enables log export via OTLP. |
EnableAspNetCoreInstrumentation |
bool |
true |
Instruments incoming HTTP requests. |
EnableHttpClientInstrumentation |
bool |
true |
Instruments outgoing HttpClient requests. |
EnableSqlClientInstrumentation |
bool |
false |
Instruments SQL calls. Opt-in β not all apps use SQL. |
EnableRuntimeInstrumentation |
bool |
true |
Collects GC, memory and thread pool metrics. |
RecordExceptions |
bool |
true |
Records exception stack traces on spans. |
ExcludedPaths |
string[] |
["/health"] |
Paths excluded from tracing. |
IncludeScopes |
bool |
true |
Includes log scopes in exported log records. |
IncludeFormattedMessage |
bool |
true |
Includes the formatted message in exported log records. |
ConfigureTracing,ConfigureMetricsandConfigureLoggingare code-only callbacks β see Code configuration.For every key with its default value, see the Full configuration reference below.
Configure entirely in code instead of appsettings.json:
builder.Services.AddTelemetry(o =>
{
o.Endpoint = new Uri("http://localhost:4318");
o.ServiceName = "my-api";
o.ResourceAttributes = new() { ["deployment.environment"] = "production" };
o.SampleRatio = 0.1;
// Code-only: register additional instrumentation
o.ConfigureTracing = tracing => tracing.AddSource("MyApp");
o.ConfigureMetrics = metrics => metrics.AddMeter("MyApp");
o.ConfigureLogging = logging => logging.AddConsole();
});Or bind appsettings.json first and layer code-only options on top β both
sources are combined, and bound values can still be overridden in the callback:
builder.Services.AddTelemetry(builder.Configuration, o =>
{
// Everything from appsettings.json is already bound here.
o.ConfigureTracing = tracing => tracing.AddSource("MyApp");
o.ConfigureMetrics = metrics => metrics.AddMeter("MyApp");
o.ConfigureLogging = logging => logging.AddConsole();
});The three callbacks are the extension points for your own telemetry. The
built-in instrumentation (ASP.NET Core, HttpClient, SQL, runtime) is wired up
automatically; these hooks let you add the signals your application emits itself.
| Hook | Builder | Used to register |
|---|---|---|
ConfigureTracing |
TracerProviderBuilder |
Activity Sources via AddSource("Name") |
ConfigureMetrics |
MeterProviderBuilder |
Meters via AddMeter("Name") |
ConfigureLogging |
ILoggingBuilder |
extra logging providers, filters, etc. |
What is a Meter?
A Meter
(from System.Diagnostics.Metrics) is the factory you create instruments
(counters, histograms, gauges) from. Each Meter has a name, and OpenTelemetry
only collects metrics from meters you have explicitly registered with
AddMeter("That.Name"). Without that call, your custom metrics are never exported.
// 1. Create a Meter and an instrument somewhere in your app
private static readonly Meter Meter = new("MyApp.Orders");
private static readonly Counter<long> OrdersPlaced =
Meter.CreateCounter<long>("orders.placed");
// ... later
OrdersPlaced.Add(1);
// 2. Register the meter's name so it gets exported
o.ConfigureMetrics = metrics => metrics.AddMeter("MyApp.Orders");What is a Source?
An ActivitySource
is the tracing equivalent: it creates Activity objects (= spans). Register its
name with AddSource("MyApp") so your custom spans are sampled and exported.
private static readonly ActivitySource Activity = new("MyApp");
using var span = Activity.StartActivity("ProcessOrder");
// ... work being traced
o.ConfigureTracing = tracing => tracing.AddSource("MyApp");The string passed to
AddMeter/AddSourcemust exactly match the name you gave theMeter/ActivitySourceβ that name is how OpenTelemetry routes the data.
Every key with its default value (only Enabled and Endpoint are required to get started):
The sample project ships a ready-to-run configuration for every supported backend. Each backend has:
- an infrastructure start script (Docker Compose or Helm) in
infrastructure/, - a launch profile that selects the matching
appsettings.<env>.json, - a UI where the exported traces, metrics and logs show up.
- Start the backend infrastructure β run the script for your backend (see table).
- Docker scripts live in
infrastructure/dockerand need Docker. - Helm scripts live in
infrastructure/helmand need a local Kubernetes cluster (e.g. k3s in WSL2).
- Docker scripts live in
- Run the sample with the matching profile:
Or pick the profile from the run dropdown in Visual Studio / Rider.
cd src/OpenTelemetryExtension.Configuration.Sample dotnet run --launch-profile "Start Aspire"
- Generate traffic β the app opens Swagger at
https://localhost:5073/swagger; call an endpoint. - Open the backend UI (see table) to inspect the telemetry.
| Backend | Start infrastructure | Launch profile | Backend UI |
|---|---|---|---|
| .NET Aspire Dashboard | infrastructure/docker/docker-install-aspire-dashboard.cmd (or Helm: helm/helm-install-aspire-dashboard.cmd) |
Start Aspire |
http://localhost:31888 |
| Jaeger | infrastructure/docker/docker-install-jaeger.cmd |
Start Jaeger |
http://localhost:16686 |
| OpenObserve | infrastructure/helm/helm-install-openobserve.cmd |
Start OpenObserve Http / Start OpenObserve Grpc |
http://localhost:30117 (admin@web.de/admin) |
Tip β viewing logs in the Aspire Dashboard: after starting the app with the
Start Aspireprofile, open http://localhost:31888, then go to the Structured (logs), Traces or Metrics tab. Data appears as soon as you hit a Swagger endpoint.
These are the exact appsettings.<env>.json files used by the sample's launch profiles.
The dashboard requires an API key on the OTLP endpoint (x-otlp-api-key). The
gRPC endpoint is exposed on NodePort 31889 (Helm) or host port 31889 (Docker).
{
"Telemetry": {
"Protocol": "Grpc",
"Endpoint": "http://localhost:31889",
"Headers": "x-otlp-api-key=aspire"
}
}Traces, metrics and logs from the sample app shown live in the Aspire Dashboard UI:
{
"Telemetry": {
"Protocol": "Grpc",
"Endpoint": "http://localhost:4317"
}
}Traces from the sample app shown in the Jaeger UI:
{
"Telemetry": {
"Protocol": "HttpProtobuf",
"Endpoint": "http://localhost:30117/api/default",
"Headers": "Authorization=Basic YWRtaW5Ad2ViLmRlOmFkbWlu,stream-name=default"
}
}The same telemetry explored in the OpenObserve UI:
- OpenTelemetry .NET β official docs Β· GitHub
- .NET observability (Microsoft Learn) β Metrics Β· Distributed tracing Β· Logging
- APIs β
MeterΒ·ActivitySource - OTLP exporter β configuration reference Β· environment variables
See CONTRIBUTING.md.




{ "Telemetry": { "Enabled": false, // master switch β set true to activate "Endpoint": "http://localhost:4318", // OTLP collector endpoint (required) "Headers": "", // exporter headers: "key1=value1,key2=value2" "Protocol": "HttpProtobuf", // "HttpProtobuf" (4318) or "Grpc" (4317) "ServiceName": null, // service name shown in the backend "ResourceAttributes": {}, // extra attributes, e.g. { "deployment.environment": "production" } "SampleRatio": 1.0, // 0.1 = 10% of traces, 1.0 = all "EnableTracing": true, // distributed tracing "EnableMetrics": true, // metrics collection "EnableLogging": true, // log export via OTLP "EnableAspNetCoreInstrumentation": true, // incoming HTTP requests "EnableHttpClientInstrumentation": true, // outgoing HttpClient requests "EnableSqlClientInstrumentation": false, // SQL calls (opt-in) "EnableRuntimeInstrumentation": true, // GC, memory, thread pool metrics "RecordExceptions": true, // exception stack traces on spans "ExcludedPaths": [ "/health" ], // paths excluded from tracing "IncludeScopes": true, // log scopes in exported records "IncludeFormattedMessage": true // formatted message in exported records } }