# .NET

> ASP.NET Core and worker services, with or without code changes

Two routes. Automatic instrumentation needs no code changes and suits an app you would
rather not rebuild. The SDK route is a few lines in `Program.cs` and gives finer control.
Both send the same OTLP data.

## Option A — automatic, no code changes

```bash
curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O
sh ./otel-dotnet-auto-install.sh

# Source it — do not execute it, or the profiler variables are lost.
. $HOME/.otel-dotnet-auto/instrument.sh

export OTEL_SERVICE_NAME="my-dotnet-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingress.rocketlog.io"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer rg_live_xxxxxx"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"

dotnet run
```

## Option B — the SDK

```bash
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
```

```csharp
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("my-dotnet-service"))
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddOtlpExporter());

builder.Logging.AddOpenTelemetry(o =>
{
    o.IncludeFormattedMessage = true;
    o.IncludeScopes = true;
    o.AddOtlpExporter();
});

var app = builder.Build();
```

`AddOtlpExporter()` with no arguments reads `OTEL_EXPORTER_OTLP_ENDPOINT`, `_HEADERS` and
`_PROTOCOL` from the environment, so the same build works in every deployment.

## Custom spans

```csharp
using System.Diagnostics;

private static readonly ActivitySource Activity = new("Payments");

using var activity = Activity.StartActivity("settle-transfer");
activity?.SetTag("transfer.id", transferId);

try
{
    await SettleAsync(transferId);
}
catch (Exception ex)
{
    activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
    activity?.AddException(ex);
    throw;
}
```

> **Warning:** .NET uses `System.Diagnostics.Activity` as its span type. An `ActivitySource` whose name
  is not registered with `AddSource()` produces **null** activities and no data — the usual
  reason custom spans go missing.
