> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracelit.io/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET SDK

> First-class OpenTelemetry instrumentation for ASP.NET Core and .NET worker apps. ILogger forwarding, GC and thread pool metrics, and HttpClient traces — all wired up with one line.

<Note>
  **Requirements:** .NET 8+
</Note>

***

## Installation

```bash theme={null}
dotnet add package Tracelit
```

***

## Quick start

<Tabs>
  <Tab title="ASP.NET Core (recommended)">
    <Steps>
      <Step title="Register Tracelit in Program.cs">
        ```csharp Program.cs theme={null}
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddTracelit(config =>
        {
            config.ApiKey      = Environment.GetEnvironmentVariable("TRACELIT_API_KEY");
            config.ServiceName = "payments-api";
            config.Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "production";
            config.SampleRate  = 1.0;
        });

        var app = builder.Build();
        app.MapGet("/", () => "Hello, Tracelit!");
        app.Run();
        ```
      </Step>

      <Step title="You're done">
        The SDK wires up traces, logs, and metrics automatically. No middleware needed.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Console / Worker apps (non-DI)">
    <Steps>
      <Step title="Configure and start manually">
        ```csharp Program.cs theme={null}
        TracelitClient.Configure(config =>
        {
            config.ApiKey      = Environment.GetEnvironmentVariable("TRACELIT_API_KEY");
            config.ServiceName = "my-worker";
            config.Environment = "production";
        });
        TracelitClient.Start();

        // ... your application code ...

        // At application exit
        TracelitClient.Shutdown();
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

***

## Configuration reference

| Option               | Env variable            | Default                       | Description                                                       |
| -------------------- | ----------------------- | ----------------------------- | ----------------------------------------------------------------- |
| `ApiKey`             | `TRACELIT_API_KEY`      | `null`                        | **Required.** Your Tracelit ingest API key                        |
| `ServiceName`        | `TRACELIT_SERVICE_NAME` | `null`                        | **Required.** Service name shown in Tracelit                      |
| `Environment`        | `TRACELIT_ENVIRONMENT`  | `"production"`                | Deployment environment tag                                        |
| `Endpoint`           | `TRACELIT_ENDPOINT`     | `https://ingest.tracelit.app` | Override only when self-hosting                                   |
| `SampleRate`         | `TRACELIT_SAMPLE_RATE`  | `1.0`                         | Head-based sampling ratio `0.0`–`1.0`. Errors always export.      |
| `Enabled`            | `TRACELIT_ENABLED`      | `true`                        | Set `false` to disable all telemetry                              |
| `ResourceAttributes` | —                       | `{}`                          | Extra `Dictionary<string, string>` on every span, metric, and log |

### Custom resource attributes

```csharp theme={null}
builder.Services.AddTracelit(config =>
{
    config.ApiKey      = Environment.GetEnvironmentVariable("TRACELIT_API_KEY");
    config.ServiceName = "orders-api";
    config.ResourceAttributes = new()
    {
        ["deployment.region"] = "us-east-1",
        ["team"]              = "platform",
    };
});
```

***

## Tracing

### Manual spans

Use the static façade — works in both DI and non-DI apps after `Start()` is called:

```csharp theme={null}
using var span = TracelitClient.Tracer.StartActiveSpan("process_payment");
span?.SetTag("payment.id", payment.Id.ToString());
span?.SetTag("payment.amount", amount.ToString());
span?.SetTag("payment.currency", currency);

try
{
    var result = ProcessPayment(payment);
    span?.SetTag("payment.status", result.Status);
    return result;
}
catch (Exception ex)
{
    span?.RecordException(ex);
    span?.SetStatus(Status.Error.WithDescription(ex.Message));
    throw;
}
```

### In ASP.NET Core (DI-injected ActivitySource)

```csharp theme={null}
public class OrdersController : ControllerBase
{
    private static readonly ActivitySource _source = new("OrdersService");

    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest req)
    {
        using var activity = _source.StartActivity("create-order");
        activity?.SetTag("order.channel", req.Channel);

        var order = await _orderService.CreateAsync(req);
        activity?.SetTag("order.id", order.Id);

        return Created($"/orders/{order.Id}", order);
    }
}
```

### Automatic instrumentation

| Library      | What is captured                                          |
| ------------ | --------------------------------------------------------- |
| ASP.NET Core | HTTP request traces, request duration/count/error metrics |
| HttpClient   | Outbound HTTP call traces                                 |
| SqlClient    | SQL query traces                                          |
| .NET runtime | GC, thread pool, lock contention metrics                  |

***

## Metrics

### Counter

```csharp theme={null}
var counter = TracelitClient.Metrics.Counter(
    "orders.placed",
    description: "Total orders placed",
    unit: "{orders}");

counter.Add(1,
    new KeyValuePair<string, object?>("currency", "USD"),
    new KeyValuePair<string, object?>("channel", "web"));
```

### Histogram

```csharp theme={null}
var histogram = TracelitClient.Metrics.Histogram(
    "external.api.duration",
    description: "External API call duration",
    unit: "ms");

var sw = Stopwatch.StartNew();
await CallExternalApiAsync();
histogram.Record(sw.Elapsed.TotalMilliseconds,
    new KeyValuePair<string, object?>("service", "stripe"));
```

### Gauge

```csharp theme={null}
var gauge = TracelitClient.Metrics.Gauge(
    "job_queue.depth",
    () => (double)JobQueue.PendingCount,
    description: "Number of pending background jobs",
    unit: "{jobs}");
```

### Automatic metrics collection

| Metric                         | Type          | Description                                   |
| ------------------------------ | ------------- | --------------------------------------------- |
| `http.server.request.duration` | Histogram     | HTTP request duration                         |
| `http.server.active_requests`  | UpDownCounter | In-flight requests                            |
| `http.client.request.duration` | Histogram     | Outbound HTTP duration                        |
| `dotnet.gc.*`                  | Various       | GC collections, heap size, pause time         |
| `dotnet.thread_pool.*`         | Various       | Thread pool queue length, worker threads      |
| `process.memory.rss`           | Gauge         | Process working set in MB (polled every 60 s) |

***

## Log forwarding

When using `AddTracelit()`, all `ILogger` output is forwarded to the Tracelit logs table via OTLP. Log records are automatically correlated to the active span via `trace_id` + `span_id`.

```csharp theme={null}
public class OrdersService
{
    private readonly ILogger<OrdersService> _logger;

    public OrdersService(ILogger<OrdersService> logger)
    {
        _logger = logger;
    }

    public async Task<Order> CreateAsync(CreateOrderRequest req)
    {
        // This log automatically includes trace_id/span_id from the current Activity
        _logger.LogInformation("Processing order for {Channel}", req.Channel);

        var order = await _repository.InsertAsync(req);

        _logger.LogInformation("Order {OrderId} created in {ElapsedMs}ms",
            order.Id, sw.ElapsedMilliseconds);

        return order;
    }
}
```

***

## Sampling and error guarantee

```csharp theme={null}
config.SampleRate = 0.1; // keep 10% of traces
```

<Note>
  **Error spans are always exported**, even when the parent trace falls outside the sample ratio. The SDK uses a custom `ErrorAlwaysOnSampler` + `ErrorSpanProcessor` pair — no configuration required.
</Note>

***

## Disabling in tests

```csharp theme={null}
// In test setup or appsettings.Testing.json
config.Enabled = false;
// or: TRACELIT_ENABLED=false
```

***

## GitHub

Source code and issue tracker: [github.com/Tracelit-AI/tracelit-dotnet](https://github.com/Tracelit-AI/tracelit-dotnet)
