/docs/opentelemetry-dotnet
ASP.NET + HttpClient + EF Core + gRPC
free plan / 1 gb-mo
telemtra.io
// .net instrumentation

OpenTelemetry .NET:
instrument ASP.NET Core with OTLP.

Add three NuGet packages, register OpenTelemetry in Program.cs, and stream traces, logs, and metrics from ASP.NET Core, HttpClient, Entity Framework, and gRPC into Telemtra.

Why OpenTelemetry for .NET

.NET has had built-in tracing primitives (System.Diagnostics.Activity) for years; OpenTelemetry .NET is the standard SDK that turns those activities into OTLP spans. With the OTel .NET SDK and the contrib instrumentations, ASP.NET Core requests, HttpClient calls, Entity Framework queries, gRPC calls, and SqlClient queries all become spans without any custom middleware. The same SDK exports metrics and logs in the same wire format — OTLP — so everything reaches Telemtra on a single endpoint.

Install NuGet packages

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore --prerelease
dotnet add package OpenTelemetry.Instrumentation.Runtime

Where this goes in your project

For a typical ASP.NET Core 8 project, the changes are: NuGet references in the .csproj, a service registration block in Program.cs, and environment variables. No new files needed.

my-service/
├── MyService.csproj        # adds OpenTelemetry.* NuGet packages
├── Program.cs              # add the AddOpenTelemetry() block from the next section
├── appsettings.json        # unchanged (optional: read endpoint from config)
└── deploy/env              # TELEMTRA_API_KEY and ASPNETCORE_ENVIRONMENT

1. Add packages via dotnet add package (the snippets in the install section, or via your IDE).

2. Add the registration block to Program.cs before builder.Build(). The exact block is in the next section — paste it after var builder = WebApplication.CreateBuilder(args);.

3. Environment variables, set via launchSettings.json for local development or your hosting provider for production:

TELEMTRA_API_KEY=tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ASPNETCORE_ENVIRONMENT=Production

That is it. No new files, no source changes outside Program.cs. The OpenTelemetry hosted service starts with the host and shuts down cleanly during graceful shutdown, which flushes pending spans.

Register OpenTelemetry in Program.cs

The using directives matter: the metrics-side AddAspNetCoreInstrumentation() lives in OpenTelemetry.Metrics, and the logs-side AddOtlpExporter() lives in OpenTelemetry.Logs. Miss either namespace and the file will not compile.

For OtlpExportProtocol.HttpProtobuf with per-signal AddOtlpExporter(...), the SDK does not append /v1/traces, /v1/metrics, or /v1/logs automatically — you must include the signal path in each Endpoint.

using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

var baseEndpoint = "https://ingest.telemtra.io";
var apiKey = Environment.GetEnvironmentVariable("TELEMTRA_API_KEY")!;
var headers = $"x-api-key={apiKey}";

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r
        .AddService("checkout-api")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = builder.Environment.EnvironmentName
        }))
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation()
        .AddSource("checkout-api")
        .AddOtlpExporter(o =>
        {
            o.Endpoint = new Uri($"{baseEndpoint}/v1/traces");
            o.Protocol = OtlpExportProtocol.HttpProtobuf;
            o.Headers = headers;
        }))
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation()
        .AddMeter("checkout-api")
        .AddOtlpExporter(o =>
        {
            o.Endpoint = new Uri($"{baseEndpoint}/v1/metrics");
            o.Protocol = OtlpExportProtocol.HttpProtobuf;
            o.Headers = headers;
        }));

builder.Logging.AddOpenTelemetry(o =>
{
    o.SetResourceBuilder(ResourceBuilder.CreateDefault()
        .AddService("checkout-api"));
    o.AddOtlpExporter(otlp =>
    {
        otlp.Endpoint = new Uri($"{baseEndpoint}/v1/logs");
        otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        otlp.Headers = headers;
    });
});

var app = builder.Build();

ASP.NET Core

AddAspNetCoreInstrumentation() wraps every incoming request in a span with route template, status code, and exception. Routes like /orders/{id} stay templated so URL cardinality does not explode.

HttpClient

AddHttpClientInstrumentation() covers every HttpClient registered through IHttpClientFactory. Outbound calls produce client spans and propagate W3C trace context so downstream services nest under the originating request.

Entity Framework Core

Each DbContext command becomes a span with the SQL text (optionally hashed for sensitive workloads). In Telemtra you can see exactly which slow query pushed a request past p95.

Manual spans via ActivitySource

private static readonly ActivitySource Activity = new("checkout-api");

public async Task SubmitOrder(string orderId, long totalCents)
{
    using var activity = Activity.StartActivity("checkout.submit_order");
    activity?.SetTag("order.id", orderId);
    activity?.SetTag("order.total_cents", totalCents);

    try
    {
        await ChargeCard(orderId, totalCents);
        await WriteLedger(orderId);
        await SendReceipt(orderId);
    }
    catch (Exception ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
        throw;
    }
}

Metrics

private static readonly Meter Meter = new("checkout-api");
private static readonly Counter<long> OrdersTotal =
    Meter.CreateCounter<long>("orders_total");
private static readonly Histogram<double> CheckoutLatencyMs =
    Meter.CreateHistogram<double>("checkout_latency_ms");

OrdersTotal.Add(1, new("plan", "team"));
CheckoutLatencyMs.Record(348);

Logs

The OpenTelemetry logging provider attaches to ILogger so every logger.LogInformation call becomes an OTLP log record. Inside an active span, trace and span IDs are attached automatically.

logger.LogInformation("order accepted {OrderId} {TotalCents}", orderId, totalCents);

Troubleshooting

  • CS1929: MeterProviderBuilder does not contain AddAspNetCoreInstrumentation. Add using OpenTelemetry.Metrics; — the metrics overload lives in that namespace.
  • CS1929: OpenTelemetryLoggerOptions does not contain AddOtlpExporter. Add using OpenTelemetry.Logs; — the logs overload lives in that namespace.
  • 404 or empty responses from the endpoint. With HttpProtobuf and per-signal AddOtlpExporter(...), the SDK does not append /v1/traces, /v1/metrics, /v1/logs. Set each exporter's Endpoint to the full signal URL.
  • 403 from Telemtra. The header is x-api-key, lowercase. Set o.Headers = "x-api-key=YOUR_KEY".
  • EF Core spans missing. The EF Core instrumentation is pre-release; include --prerelease on dotnet add package.
  • Logs not arriving. Confirm builder.Logging.AddOpenTelemetry is called and the OTLP exporter uses the /v1/logs endpoint with the same headers as traces.
  • Runtime metrics missing. Add OpenTelemetry.Instrumentation.Runtime and register it with .AddRuntimeInstrumentation().

For protocol details see What is OTLP?. For a Collector sidecar, follow the Collector quickstart. New to Telemtra? The quickstart shows the minimum Program.cs registration. Create a Telemtra workspace to get an API key.

OpenTelemetry .NET FAQ

Does OpenTelemetry .NET work with ASP.NET Core 6, 7, 8, and 9?

Yes. OpenTelemetry.Extensions.Hosting integrates with the generic host on every supported .NET version. ASP.NET Core requests, HttpClient calls, Entity Framework queries, and gRPC calls become spans through the matching OpenTelemetry.Instrumentation.* NuGet packages.

Do I need a separate Telemtra SDK or NuGet package?

No. Telemtra accepts standard OpenTelemetry over OTLP. Use OpenTelemetry.Exporter.OpenTelemetryProtocol with OtlpExportProtocol.HttpProtobuf, point the endpoint at https://ingest.telemtra.io, and set the x-api-key header.

Does System.Diagnostics.Activity work with OpenTelemetry .NET?

Yes. .NET has had Activity-based tracing for years; OpenTelemetry .NET maps each ActivitySource to spans. Use ActivitySource.StartActivity for manual spans and they will export over OTLP alongside the auto-instrumented ones.

Are ILogger logs sent to OpenTelemetry?

Yes if you call builder.Logging.AddOpenTelemetry(o => o.AddOtlpExporter(...)). Every logger.LogInformation / LogWarning / LogError becomes an OTLP log record. Inside an active span, trace and span IDs are attached automatically so logs link back to their trace in Telemtra.

What about Entity Framework Core spans?

Add OpenTelemetry.Instrumentation.EntityFrameworkCore (currently --prerelease) and register it with .AddEntityFrameworkCoreInstrumentation(). Each EF Core query becomes a span with the SQL command text (truncated by default) and database connection details.

Further reading