$npx -y skills add managedcode/dotnet-skills --skill configuring-opentelemetry-dotnetConfigure OpenTelemetry distributed tracing, metrics, and logging in ASP.NET Core using the .NET OpenTelemetry SDK. Use when adding observability, setting up OTLP exporters, creating custom metrics/spans, or troubleshooting distributed trace correlation.
| 1 | # Configuring OpenTelemetry in .NET |
| 2 | |
| 3 | ## When to Use |
| 4 | |
| 5 | - Adding distributed tracing to an ASP.NET Core application |
| 6 | - Setting up OpenTelemetry exporters (OTLP is the primary protocol; Jaeger accepts OTLP natively; Prometheus OTLP ingestion requires explicit opt-in) |
| 7 | - Creating custom metrics or trace spans for business operations |
| 8 | - Troubleshooting distributed trace context propagation across services |
| 9 | |
| 10 | ## When Not to Use |
| 11 | |
| 12 | - The user wants application-level logging only (use ILogger, Serilog) |
| 13 | - The user is using Application Insights SDK directly (different API) |
| 14 | - The user needs APM with a commercial vendor's proprietary SDK |
| 15 | |
| 16 | ## Inputs |
| 17 | |
| 18 | | Input | Required | Description | |
| 19 | |-------|----------|-------------| |
| 20 | | ASP.NET Core project | Yes | The application to instrument | |
| 21 | | Observability backend | No | Where to export: OTLP collector, Aspire dashboard, Jaeger (accepts OTLP natively) | |
| 22 | |
| 23 | ## Workflow |
| 24 | |
| 25 | ### Step 1: Install the correct packages |
| 26 | |
| 27 | **There are many OpenTelemetry NuGet packages. Install exactly these:** |
| 28 | |
| 29 | ```bash |
| 30 | # Core SDK + ASP.NET Core instrumentation + logging integration |
| 31 | dotnet add package OpenTelemetry.Extensions.Hosting |
| 32 | dotnet add package OpenTelemetry.Instrumentation.AspNetCore |
| 33 | dotnet add package OpenTelemetry.Instrumentation.Http |
| 34 | |
| 35 | # Exporter |
| 36 | dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol # OTLP exporter for traces, metrics, AND logs |
| 37 | |
| 38 | # Optional — dev/local debugging only (do NOT include in production deployments) |
| 39 | # dotnet add package OpenTelemetry.Exporter.Console |
| 40 | ``` |
| 41 | |
| 42 | **Do NOT install `OpenTelemetry` alone** — you need `OpenTelemetry.Extensions.Hosting` for proper DI integration. |
| 43 | |
| 44 | #### Optional: additional auto-instrumentation packages |
| 45 | |
| 46 | Install only the packages that match the libraries your application uses: |
| 47 | |
| 48 | ```bash |
| 49 | dotnet add package OpenTelemetry.Instrumentation.SqlClient # SQL Server queries |
| 50 | dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore # EF Core |
| 51 | dotnet add package OpenTelemetry.Instrumentation.GrpcNetClient # gRPC calls |
| 52 | dotnet add package OpenTelemetry.Instrumentation.Runtime # GC, thread pool metrics |
| 53 | ``` |
| 54 | |
| 55 | ### Step 2: Configure all signals in Program.cs |
| 56 | |
| 57 | ```csharp |
| 58 | using OpenTelemetry.Resources; |
| 59 | using OpenTelemetry.Trace; |
| 60 | using OpenTelemetry.Metrics; |
| 61 | using OpenTelemetry.Logs; |
| 62 | |
| 63 | var builder = WebApplication.CreateBuilder(args); |
| 64 | |
| 65 | builder.Services.AddOpenTelemetry() |
| 66 | .ConfigureResource(resource => resource |
| 67 | .AddService(serviceName: builder.Environment.ApplicationName)) |
| 68 | .WithTracing(tracing => tracing |
| 69 | .AddAspNetCoreInstrumentation(options => |
| 70 | { |
| 71 | // Filter out health check endpoints from traces |
| 72 | options.Filter = httpContext => |
| 73 | !httpContext.Request.Path.StartsWithSegments("/healthz"); |
| 74 | }) |
| 75 | .AddHttpClientInstrumentation(options => |
| 76 | { |
| 77 | options.RecordException = true; |
| 78 | }) |
| 79 | // Optional: add SQL instrumentation if using SqlClient directly |
| 80 | // .AddSqlClientInstrumentation(options => |
| 81 | // { |
| 82 | // options.SetDbStatementForText = true; |
| 83 | // options.RecordException = true; |
| 84 | // }) |
| 85 | // Custom activity sources (must match ActivitySource names in your code) |
| 86 | .AddSource("MyApp.Orders") |
| 87 | .AddSource("MyApp.Payments") |
| 88 | .AddSource("MyApp.Messaging")) |
| 89 | .WithMetrics(metrics => metrics |
| 90 | .AddAspNetCoreInstrumentation() |
| 91 | .AddHttpClientInstrumentation() |
| 92 | // Optional: .AddRuntimeInstrumentation() for GC and thread pool metrics |
| 93 | // (requires OpenTelemetry.Instrumentation.Runtime package) |
| 94 | // Custom meters (must match Meter names in your code) |
| 95 | .AddMeter("MyApp.Metrics")) |
| 96 | .WithLogging(logging => |
| 97 | { |
| 98 | logging.IncludeScopes = true; |
| 99 | // logging.IncludeFormattedMessage = true; // Enable if you need the formatted message string in log exports |
| 100 | }) |
| 101 | // Single OTLP exporter for all signals — reads OTEL_EXPORTER_OTLP_ENDPOINT |
| 102 | // env var (defaults to http://localhost:4317). Override via environment variable |
| 103 | // or appsettings.json configuration. |
| 104 | .UseOtlpExporter(); |
| 105 | ``` |
| 106 | |
| 107 | ### Step 3: Understanding log–trace correlation |
| 108 | |
| 109 | The `.WithLogging()` call in Step 2 integrates ILogger with OpenTelemetry: |
| 110 | |
| 111 | - Each log entry automatically includes TraceId and SpanId for correlation with traces |
| 112 | - The service resource from `.ConfigureResource()` propagates to logs automatically |
| 113 | - `UseOtlpExporter()` applies to logs alongside traces and metrics |
| 114 | - No additional packages or separate `SetResourceBuilder` call needed |
| 115 | |
| 116 | ### Step 4: Create custom spans (Activities) for business operations |
| 117 | |
| 118 | ```csharp |
| 119 | using System.Diag |