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

# Send .NET logs and traces to Bronto via OpenTelemetry

> Instrument .NET and ASP.NET Core applications with the OpenTelemetry .NET SDK to send logs and traces to Bronto over OTLP/HTTP via a Collector.

This page covers instrumenting a .NET application with the OpenTelemetry SDK to send **logs and traces** to Bronto over OTLP/HTTP via a local OTel Collector. Both signals are co-equal: logs are bridged from `ILogger` — no log statement changes needed — and traces are emitted via a tracer provider.

<Tip>
  If you don't have a Collector and want to export directly from your application to Bronto, see [Direct export to Bronto](#direct-export-to-bronto) at the bottom of this page.
</Tip>

## Prerequisites

* .NET 6 or later
* NuGet
* A running OTel Collector configured to forward logs and traces to Bronto — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry)
* [OpenTelemetry .NET SDK documentation](https://opentelemetry.io/docs/languages/dotnet/)

## Install dependencies

```bash theme={"dark"}
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
```

| Package                                        | Purpose                                         |
| ---------------------------------------------- | ----------------------------------------------- |
| `OpenTelemetry`                                | Core OTel SDK                                   |
| `OpenTelemetry.Extensions.Hosting`             | `ILogger` bridge and hosted service integration |
| `OpenTelemetry.Exporter.OpenTelemetryProtocol` | OTLP/HTTP exporter                              |

## Configure the log bridge

The OTel .NET SDK integrates with `Microsoft.Extensions.Logging` via `AddOpenTelemetry()`. Every log record emitted through `ILogger` is forwarded to the OTel pipeline automatically.

For ASP.NET Core or any host using `Microsoft.Extensions.Hosting`, configure logging in `Program.cs`:

```csharp Program.cs theme={"dark"}
builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});
```

`IncludeFormattedMessage` attaches the rendered log message as the OTel record body. `IncludeScopes` propagates any active `ILogger` scope values as structured attributes.

## Configure the OTLP exporter

Wire the OTLP exporter into the logging pipeline. By default the OTel Collector listens for OTLP/HTTP on port `4318`.

```csharp Program.cs theme={"dark"}
builder.Services.AddOpenTelemetry()
    .WithLogging(logs =>
    {
        logs.AddOtlpExporter(otlp =>
        {
            otlp.Endpoint = new Uri("http://localhost:4318/v1/logs");
            otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    });
```

If your Collector runs on a different host or port, update the endpoint accordingly.

## Set resource attributes

Resource attributes are attached to every log record exported from this process. Two attributes drive how Bronto organises incoming logs:

| OTel attribute      | Bronto concept | Description                                  |
| ------------------- | -------------- | -------------------------------------------- |
| `service.name`      | Dataset        | Groups logs from one service                 |
| `service.namespace` | Collection     | Groups related services or a team's services |

Set them via `ConfigureResource` on the `OpenTelemetryBuilder`:

```csharp Program.cs theme={"dark"}
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(
            serviceName: "my-service",
            serviceNamespace: "my-team")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = "production",
        }))
    .WithLogging(logs =>
    {
        logs.AddOtlpExporter(otlp =>
        {
            otlp.Endpoint = new Uri("http://localhost:4318/v1/logs");
            otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    });
```

## Complete example

The snippet below shows the full `Program.cs` setup for an ASP.NET Core application.

```csharp Program.cs theme={"dark"}
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(
            serviceName: "my-service",
            serviceNamespace: "my-team")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = "production",
        }))
    .WithLogging(logs =>
    {
        logs.AddOtlpExporter(otlp =>
        {
            otlp.Endpoint = new Uri("http://localhost:4318/v1/logs");
            otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    });

var app = builder.Build();

// ILogger usage — no changes needed to existing code
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Application started");
logger.LogWarning("Low disk space {DiskFreeGb}", 2.1);

app.Run();
```

<Note>
  For non-hosted applications (console apps, workers), use `LoggerFactory.Create` with the same `AddOpenTelemetry` and `AddOtlpExporter` calls instead of `builder.Logging`.
</Note>

## Verify delivery

After running your application, check both signals in Bronto:

* **Logs**: open the [Search](https://app.bronto.io/search) page and filter by the dataset name you set in `service.name` — your log records should appear within a few seconds.
* **Traces**: open the Explore Traces page and filter by the same `service.name` — your spans should appear within a few seconds.

If no logs appear, check:

* The OTel Collector is running and reachable at the configured endpoint.
* The Collector's pipeline includes a `logs` pipeline with an `otlp` receiver and the Bronto exporter — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry).
* The `OtlpExportProtocol.HttpProtobuf` protocol matches the Collector's configured receiver.

## Traces

<Tip>
  For ASP.NET Core, HTTP clients, and Entity Framework Core, add the corresponding NuGet instrumentation packages — `OpenTelemetry.Instrumentation.AspNetCore`, `OpenTelemetry.Instrumentation.Http`, `OpenTelemetry.Instrumentation.EntityFrameworkCore` — then call `.AddAspNetCoreInstrumentation()`, `.AddHttpClientInstrumentation()`, etc. inside `WithTracing`. See [.NET instrumentation libraries](https://opentelemetry.io/docs/languages/dotnet/libraries/) for the full list.
</Tip>

### Configure the tracer provider

No additional packages are needed — `OpenTelemetry.Extensions.Hosting` and `OpenTelemetry.Exporter.OpenTelemetryProtocol` already include tracing support.

Add `WithTracing` to the same `AddOpenTelemetry` call you used for logging:

```csharp Program.cs theme={"dark"}
builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(
            serviceName: "my-service",
            serviceNamespace: "my-team")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = "production",
        }))
    .WithLogging(logs =>
    {
        logs.AddOtlpExporter(otlp =>
        {
            otlp.Endpoint = new Uri("http://localhost:4318/v1/logs");
            otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        });
    })
    .WithTracing(tracing =>
    {
        tracing
            .AddSource("my-service")
            .AddOtlpExporter(otlp =>
            {
                otlp.Endpoint = new Uri("http://localhost:4318/v1/traces");
                otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
            });
    });
```

`ConfigureResource` applies to both logs and traces — `service.name` and `service.namespace` are shared automatically.

### Creating spans

Inject `ActivitySource` and use it to create spans around operations you want to trace:

```csharp theme={"dark"}
private static readonly ActivitySource ActivitySource = new("my-service");

public async Task<IActionResult> ProcessPayment(PaymentRequest request)
{
    using var activity = ActivitySource.StartActivity("process-payment");
    activity?.SetTag("payment.amount", request.Amount);
    activity?.SetTag("payment.currency", request.Currency);

    _logger.LogInformation("Processing payment");  // trace_id injected automatically
    // your code here
    return Ok();
}
```

Any `ILogger` call made inside an active `Activity` span will have `trace_id` and `span_id` injected automatically.

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry defines [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for model, token usage, and prompt/response content.

<Note>
  This page was verified on **July 17, 2026**. GenAI libraries and semantic conventions are evolving rapidly, so package configuration and emitted attributes can change between releases. Keep your instrumentation current and verify the fields emitted by the version you deploy.
</Note>

### Auto-instrumentation

Several .NET AI libraries provide GenAI telemetry, but each has its own opt-in:

* **Semantic Kernel**: enable `SEMANTICKERNEL_EXPERIMENTAL_GENAI_ENABLE_OTEL_DIAGNOSTICS=true`, then add its activity source:

```csharp theme={"dark"}
tracing.AddSource("Microsoft.SemanticKernel*")
```

* **OpenAI .NET SDK**: enable its experimental OpenTelemetry switch with `AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true)`, then add `OpenAI.*` as a source.
* **Microsoft.Extensions.AI**: wrap an `IChatClient` with `UseOpenTelemetry()`; set `EnableSensitiveData` when you intentionally want message content recorded.
* **AWS SDK**: `OpenTelemetry.Instrumentation.AWS` covers Bedrock Runtime and Agent Runtime clients.

Check each library's current documentation because these APIs are experimental and version-sensitive.

### Experimental: capturing prompt and response content

Content capture is configured by the .NET library rather than the cross-language OTel environment variables. For Semantic Kernel, use the sensitive diagnostics opt-in:

```bash theme={"dark"}
export SEMANTICKERNEL_EXPERIMENTAL_GENAI_ENABLE_OTEL_DIAGNOSTICS_SENSITIVE=true
```

<Warning>
  Prompt and response content can contain sensitive data. Microsoft.Extensions.AI uses its `EnableSensitiveData` option instead; the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` variable used by some JavaScript, Java, and Python instrumentations is not a .NET-wide switch.
</Warning>

### Manual spans

```csharp theme={"dark"}
private static readonly ActivitySource ActivitySource = new("my-service");

using var activity = ActivitySource.StartActivity("chat gpt-4o-mini");
activity?.SetTag("gen_ai.provider.name", "openai");
activity?.SetTag("gen_ai.request.model", "gpt-4o-mini");
activity?.SetTag("gen_ai.usage.input_tokens", 33);
activity?.SetTag("gen_ai.usage.output_tokens", 74);
activity?.SetTag("gen_ai.response.finish_reasons", new[] { "stop" });
```

See [LLM Observability](/ai-features/llm-observability) for the full recommended attribute set, including `gen_ai.input.messages` / `gen_ai.output.messages`, and how to search and aggregate GenAI fields in Bronto.

## Direct export to Bronto

If you are not using an OTel Collector, export directly to Bronto by replacing the exporter endpoints and adding your API key:

```csharp theme={"dark"}
.WithLogging(logs =>
{
    logs.AddOtlpExporter(otlp =>
    {
        otlp.Endpoint = new Uri("https://ingestion.eu.bronto.io/v1/logs"); // or ingestion.us.bronto.io
        otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        otlp.Headers = "x-bronto-api-key=<YOUR_API_KEY>";
    });
})
.WithTracing(tracing =>
{
    tracing.AddSource("my-service").AddOtlpExporter(otlp =>
    {
        otlp.Endpoint = new Uri("https://ingestion.eu.bronto.io/v1/traces"); // or ingestion.us.bronto.io
        otlp.Protocol = OtlpExportProtocol.HttpProtobuf;
        otlp.Headers = "x-bronto-api-key=<YOUR_API_KEY>";
    });
});
```

| Region | Logs endpoint                            | Traces endpoint                            |
| ------ | ---------------------------------------- | ------------------------------------------ |
| EU     | `https://ingestion.eu.bronto.io/v1/logs` | `https://ingestion.eu.bronto.io/v1/traces` |
| US     | `https://ingestion.us.bronto.io/v1/logs` | `https://ingestion.us.bronto.io/v1/traces` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.
