Skip to main content
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. Logs are bridged from ILogger — no log statement changes needed. Traces are added by configuring a tracer provider alongside the log provider.
If you don’t have a Collector and want to export directly from your application to Bronto, see Direct export to Bronto at the bottom of this page.

Prerequisites

Install dependencies

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
PackagePurpose
OpenTelemetryCore OTel SDK
OpenTelemetry.Extensions.HostingILogger bridge and hosted service integration
OpenTelemetry.Exporter.OpenTelemetryProtocolOTLP/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:
Program.cs
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.
Program.cs
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 attributeBronto conceptDescription
service.nameDatasetGroups logs from one service
service.namespaceCollectionGroups related services or a team’s services
Set them via ConfigureResource on the OpenTelemetryBuilder:
Program.cs
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.
Program.cs
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();
For non-hosted applications (console apps, workers), use LoggerFactory.Create with the same AddOpenTelemetry and AddOtlpExporter calls instead of builder.Logging.

Verify log collection

After running your application, open the Search page in Bronto. Filter by the dataset name you set in service.name — your log records 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.
  • The OtlpExportProtocol.HttpProtobuf protocol matches the Collector’s configured receiver.

Traces

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 for the full list.

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:
Program.cs
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:
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.

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:
.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>";
    });
});
RegionLogs endpointTraces endpoint
EUhttps://ingestion.eu.bronto.io/v1/logshttps://ingestion.eu.bronto.io/v1/traces
UShttps://ingestion.us.bronto.io/v1/logshttps://ingestion.us.bronto.io/v1/traces
See API Keys for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.