# Instrument ASP.NET Core with OpenTelemetry

> Send traces, logs and metrics from an ASP.NET Core app to Telemetry Machine with the OpenTelemetry .NET SDK, in five lines of setup.

The OpenTelemetry .NET SDK traces every request and outgoing HTTP call, sends your `ILogger` records as logs, and records ASP.NET Core's HTTP and Kestrel metrics.

## Create the datasets

Each signal goes to its own dataset. Create three, and an ingest key that can write to all of them, with [tm-cli](https://telemetrymachine.com/docs/quick-start#tm-cli) or the [MCP server](https://telemetrymachine.com/docs/quick-start#option-1-set-up-with-a-coding-agent):

```console tab="tm-cli"
$ tm-cli datasets create --name shop-prod-traces --type traces
$ tm-cli datasets create --name shop-prod-logs --type logs
$ tm-cli datasets create --name shop-prod-metrics --type metrics
$ tm-cli api-keys create --name shop-api --dataset shop-prod-traces --dataset shop-prod-logs --dataset shop-prod-metrics
```

```json tab="MCP"
create_dataset { "name": "shop-prod-traces", "signal_type": "traces" }
create_dataset { "name": "shop-prod-logs", "signal_type": "logs" }
create_dataset { "name": "shop-prod-metrics", "signal_type": "metrics" }
create_ingest_key {
  "name": "shop-api",
  "datasets": ["shop-prod-traces", "shop-prod-logs", "shop-prod-metrics"]
}
```

tm-cli shows the key once, in `api_key.api_key`. Over MCP, your agent gets it once and saves it in your app's configuration. In the app, [create each dataset](https://telemetrymachine.com/datasets/new), then a key for all three under **API keys** in your organization's settings.

## Install

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

Add OpenTelemetry to your services in `Program.cs`:

```csharp title="Program.cs" {1-3,7-11}
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
    .WithMetrics(metrics => metrics.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation())
    .WithLogging()
    .UseOtlpExporter();

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

## Configure

```bash
export TM_INGEST_KEY='<ingest-key>'
export OTEL_SERVICE_NAME=shop-api
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.telemetrymachine.com
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-traces"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-logs"
export OTEL_EXPORTER_OTLP_METRICS_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-metrics"
```

## Run

```console
$ dotnet run
```

## Check it arrived

Send your app a few requests, then open each explorer and choose the dataset:

- **Traces**, `shop-prod-traces`: one server span per request, named after its route, such as `GET /items/{id}`.
- **Logs**, `shop-prod-logs`: your `ILogger` records, each with its request's `trace.trace_id`.
- **Metrics**, `shop-prod-metrics`: `http.server.request.duration` and the `kestrel.*` metrics, sent once a minute.

Or count spans by name with tm-cli or the MCP server:

```console tab="tm-cli"
$ tm-cli query shop-prod-traces --since 15m --select "name, count(*)" --group-by name
```

```json tab="MCP"
run_query {
  "dataset": "shop-prod-traces",
  "description": "Spans by name",
  "since": "15m",
  "select": "name, count(*)",
  "group_by": "name"
}
```

## Troubleshooting

- **Nothing arrives, and nothing in the console:** set `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`. The .NET exporter defaults to gRPC, which Telemetry Machine doesn't accept, and fails without a word.
- **`Console.WriteLine` output isn't in Logs:** only `ILogger` records are sent.

---

All pages: https://telemetrymachine.com/docs/llms.txt
