OpenTelemetry .NET instrumentation adds vendor-neutral traces and metrics to ASP.NET Core request handling. It is useful when a web service needs to send route names, status codes, and request-duration measurements to an OpenTelemetry Collector before a vendor backend is chosen.
The ASP.NET Core instrumentation package hooks into the hosting pipeline through AddAspNetCoreInstrumentation(). The OTLP exporter sends collected telemetry to the Collector on port 4317, and the Collector debug exporter prints the received spans and metric points so the first request can be inspected before connecting a production backend.
Local instrumentation testing needs a .NET SDK 8 or newer project and Docker for the Collector. The sample service name and route are neutral; replace serviceName and the exporter endpoint for the real service after the local trace and HTTP server metric appear in Collector output.
$ dotnet new web -o aspnetcoreapp The template "ASP.NET Core Empty" was created successfully. ##### snipped ##### Restore succeeded.
$ cd aspnetcoreapp
$ dotnet add package \ OpenTelemetry.Extensions.Hosting
$ dotnet add package \ OpenTelemetry.Instrumentation.AspNetCore
$ dotnet add package \ OpenTelemetry.Exporter.OpenTelemetryProtocol
using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; const string serviceName = "orders-api"; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenTelemetry() .ConfigureResource(resource => resource.AddService(serviceName: serviceName)) .WithTracing(tracing => tracing .AddAspNetCoreInstrumentation() .AddOtlpExporter()) .WithMetrics(metrics => metrics .AddAspNetCoreInstrumentation() .AddOtlpExporter((exporterOptions, metricReaderOptions) => { var readerOptions = metricReaderOptions.PeriodicExportingMetricReaderOptions; readerOptions.ExportIntervalMilliseconds = 1000; })); var app = builder.Build(); app.MapGet("/orders/{id:int}", (int id) => Results.Ok(new { id, status = "accepted" })); app.Run();
The one-second metric export interval keeps the local smoke test short. Use a normal export interval for service traffic after the first Collector check is complete.
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 exporters: debug: verbosity: detailed service: pipelines: traces: receivers: [otlp] exporters: [debug] metrics: receivers: [otlp] exporters: [debug]
Use debug for local inspection only because it writes telemetry attributes to container logs.
Related: How to test OpenTelemetry Collector pipelines with the debug exporter
Tool: OpenTelemetry Collector Config Generator
$ docker run -d --rm --name otelcol-debug \ -p 4317:4317 \ -p 4318:4318 \ -v "$PWD/collector-config.yaml:/etc/otelcol/config.yaml:ro" \ otel/opentelemetry-collector:latest
Keep this container running while the ASP.NET Core app sends telemetry.
Related: How to run the OpenTelemetry Collector in Docker
$ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
$ dotnet run --urls http://localhost:5080 Building... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5080 ##### snipped #####
AddOtlpExporter() uses OTLP/gRPC by default, which matches the Collector listener on port 4317.
$ curl -s http://localhost:5080/orders/42 {"id":42,"status":"accepted"}
$ docker logs otelcol-debug
Resource attributes:
-> service.name: Str(orders-api)
-> telemetry.sdk.language: Str(dotnet)
InstrumentationScope Microsoft.AspNetCore
Span #0
Name : GET /orders/{id:int}
Kind : Server
Attributes:
-> http.request.method: Str(GET)
-> http.route: Str(/orders/{id:int})
-> http.response.status_code: Int(200)
##### snipped #####
Metric #1
Descriptor:
-> Name: http.server.request.duration
The trace shows request instrumentation is active. The http.server.request.duration metric shows the ASP.NET Core server metric pipeline is also reaching the Collector.
$ docker stop otelcol-debug otelcol-debug