Send Data with the Honeycomb Distribution for .NET

Warning

While the Honeycomb distributions of the OpenTelemetry SDKs are not yet deprecated, they are in maintenance.

Honeycomb provides the Honeycomb OpenTelemetry Distribution for .NET to help you instrument your applications and send telemetry data to Honeycomb as quickly and easily as possible. Under the hood, the Honeycomb Distribution uses OpenTelemetry for .NET, so advanced users or those who have already instrumented their applications with OpenTelemetry do not need to use this Distribution.

The Honeycomb Distribution reads variables you provide and translates them to variables understood by the upstream OpenTelemetry SDK. For example, the Honeycomb Distribution automatically converts the environment variable HONEYCOMB_API_ENDPOINT to the OpenTelemetry variable OTEL_EXPORTER_OTLP_ENDPOINT. If you want to send data to Honeycomb using OpenTelemetry without the Honeycomb Distribution, you will need to configure your implementation to match variables expected by OpenTelemetry.

In this guide, we explain how to set up automatic and custom, or manual, instrumentation for a service written in .NET. If you prefer learning by example, we provide several examples of applications configured to send OpenTelemetry data to Honeycomb using the Honeycomb OpenTelemetry Distribution for .NET.

Before You Begin 

Before you can set up instrumentation for your .NET application, you will need to do a few things.

Prepare Your Development Environment 

To complete the required steps, you will need:

  • A working .NET environment
  • A .NET application that conforms to NET Standard 2.0, or a .NET Framework 4.6.2+ application.

Get Your Honeycomb API Key 

To send data to Honeycomb, you’ll need to sign up for a free Honeycomb account and create a Honeycomb Ingest API Key. To get started, you can create a key that you expect to swap out when you deploy to production. Name it something helpful, perhaps noting that it’s a getting started key. Make note of your API key; for security reasons, you will not be able to see the key again, and you will need it later!

Tip
For setup, make sure you check the “Can create datasets” checkbox so that your data will show up in Honeycomb. Later, when you replace this key with a permanent one, you can uncheck that box.

If you want to use an API key you previously stored in a secure location, you can also look up details for Honeycomb API Keys any time in your Environment Settings, and use them to retrieve keys from your storage location.

Add Automatic Instrumentation 

Acquire Dependencies 

Install the Honeycomb.OpenTelemetry NuGet package.

For example, with the .NET CLI, use:

dotnet add package Honeycomb.OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package Honeycomb.OpenTelemetry.CommonInstrumentations --prerelease

Initialize and Configure 

Configure your application for Honeycomb OpenTelemetry Distribution for .NET using one of the following configuration methods:

Initialize your ASP.NET Core app to use the Honeycomb OpenTelemetry Distribution for .NET:

  1. In your appsettings.json file, add a Honeycomb section:

    {
      "Honeycomb": {
        "ServiceName": "my-app",
        "ApiKey": "{apikey}"
      }
    }
    

    By default, the data you send will be stored in our US data location (https://api.honeycomb.io:443). If you would like to store your data in our EU location, configure the Endpoint as well:

    {
      "Honeycomb": {
        "Endpoint": "https://api.eu1.honeycomb.io:443",
        "ServiceName": "my-app",
        "ApiKey": "{apikey}"
      }
    }
    
  2. Get HoneycombOptions from configuration and use these options to configure the OpenTelemetry SDK in your application startup code:

    using OpenTelemetry.Trace;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddControllers();
    
    var honeycombOptions = builder.Configuration.GetHoneycombOptions();
    
    // Set up OpenTelemetry Tracing
    builder.Services.AddOpenTelemetry().WithTracing(otelBuilder =>
    {
        otelBuilder
            .AddHoneycomb(honeycombOptions)
            .AddCommonInstrumentations();
    });
    
    // Register a Tracer, so it can be injected into other components (for example, Controllers)
    builder.Services.AddSingleton(TracerProvider.Default.GetTracer(honeycombOptions.ServiceName));
    
    var app = builder.Build();
    
    app.MapGet("/", (Tracer tracer) =>
    {
        using var span = tracer.StartActiveSpan("app.manual-span");
        span.SetAttribute("app.manual-span.message", "Adding custom spans is also super easy!");
    });
    
    await app.RunAsync();
    
Note

If you use Honeycomb Classic user, you must also specify the Dataset using the Dataset field in the appsettings.json file.

{
  "Honeycomb": {
    "ServiceName": "my-app",
    "ApiKey": "{apikey}",
    "Dataset": "{dataset}"
  }
}

To programmatically configure the OpenTelemetry SDK, provide a HoneycombOptions object:

using OpenTelemetry;
using Honeycomb.OpenTelemetry;

var options = new HoneycombOptions
{
    Endpoint = "https://api.honeycomb.io:443" // US instance
    //Endpoint = "https://api.eu1.honeycomb.io:443" // EU instance
    ServiceName = "my-app",
    ServiceVersion = "1.0.0",
    ApiKey = "{apikey}",
    ResourceBuilder = ResourceBuilder.CreateDefault().AddAttributes(
        new Dictionary<string, object>
        {
            {"custom-resource-attribute", "some-value"}
        })
};

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddHoneycomb(options)
    .Build();
Note

If you use Honeycomb Classic user, you must also specify the Dataset using the Dataset field in the HoneycombOptions object.

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddHoneycomb(new HoneycombOptions
    {
        Endpoint = "https://api.honeycomb.io:443" // US instance
        //Endpoint = "https://api.eu1.honeycomb.io:443" // EU instance
        ServiceName = "my-app",
        ApiKey = "{apikey}"
        Dataset = "{dataset}"
    })
    .Build();

Add the following to your Web.config:

<system.webServer>
    <modules>
        <add
            name="TelemetryHttpModule"
            type="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule,
                OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule"
            preCondition="integratedMode,managedHandler" />
    </modules>
</system.webServer>

Configure the OpenTelemetry SDK to use Honeycomb during application start:

using OpenTelemetry;
using OpenTelemetry.Trace;
using Honeycomb.OpenTelemetry;

public class WebApiApplication : HttpApplication
{
    private TracerProvider _tracerProvider;

    protected void Application_Start()
    {
        _tracerProvider = Sdk.CreateTracerProviderBuilder()
            .AddHoneycomb(new HoneycombOptions
            {
                Endpoint = "https://api.honeycomb.io:443" // US instance
                //Endpoint = "https://api.eu1.honeycomb.io:443" // EU instance
                ServiceName = "my-app",
                ApiKey = "{apikey}"
            })
            .Build();
    }

    protected void Application_End()
    {
        _tracerProvider?.Dispose();
    }
}

To learn more about advanced configuration options, see the OpenTelemetry ASP.NET instrumentation.

Note

If you use Honeycomb Classic user, you must also specify the Dataset using the Dataset field in the HoneycombOptions object.

_tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddHoneycomb(new HoneycombOptions
    {
        ServiceName = "my-app",
        ApiKey = "{apikey}",
        Dataset = "{dataset}"
    })
    .Build();

Run 

Run your application. You will see the incoming requests and outgoing HTTP calls generate traces.

dotnet run

In Honeycomb’s UI, you should now see your application’s incoming requests and outgoing HTTP calls generate traces.

Using HTTP/protobuf instead of gRPC 

By default, the OpenTelemetry .NET SDK uses the gRPC protocol to send telemetry data. To use HTTP instead of gRPC for traces, update the protocol in the OtlpExporter:

services.AddOpenTelemetry().WithTracing(builder => builder
    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName))
    .AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation()
    .AddOtlpExporter(option =>
    {
        option.Endpoint = new Uri("https://api.honeycomb.io/v1/traces"); // US instance
        //option.Endpoint = new Uri("https://api.eu1.honeycomb.io/v1/traces"); // EU instance
        option.Headers = $"x-honeycomb-team={honeycombApiKey}";
        option.Protocol = OtlpExportProtocol.HttpProtobuf;
    }));

Note that the endpoint URL is different for HTTP/protobuf than for gRPC in that it specifies the signal type (/v1/traces).

If the application is running on .NET Core 3.x, also add the following at application startup:

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
Note

If you are sending data to Honeycomb directly, use the endpoint configuration options and OpenTelemetry Headers listed in the OpenTelemetry for .NET chart.

If you are using an OpenTelemetry Collector, specify the endpoint of the collector, and add the headers to the collector configuration file.

Using Instrumentation Packages 

By default, the Honeycomb Distribution does not bundle any instrumentation packages. However, two additional packages exist to make getting started easier:

  • Honeycomb.OpenTelemetry.Instrumentation.AspNetCore
  • Honeycomb.OpenTelemetry.CommonInstrumentations

The Honeycomb.OpenTelemetry.Instrumentation.AspNetCore package wraps the existing ASP.NET Core instrumentation package published by OpenTelemetry, but also copies the current trace baggage into spans created on incoming requests. If you’d prefer not to have this behavior, you can use the OpenTelemetry AspNetCore instrumentation package directly.

To use Honeycomb.OpenTelemetry.Instrumentation.AspNetCore, first add the package:

dotnet add package Honeycomb.OpenTelemetry.Instrumentation.AspNetCore --prerelease

Then, configure the OpenTelemetry SDK to add ASP.NET Core instrumentation during application start:

using OpenTelemetry.Trace;

var honeycombOptions = builder.Configuration.GetHoneycombOptions();

builder.Services.AddOpenTelemetry().WithTracing(otelBuilder =>
    otelBuilder
        .AddHoneycomb(honeycombOptions)
        .AddAspNetCoreInstrumentationWithBaggage()
);

The Honeycomb.OpenTelemetry.CommonInstrumentations package bundles several common instrumentation libraries:

For .NET 6 and above:

  • Honeycomb.OpenTelemetry.Instrumentation.AspNetCore
  • OpenTelemetry.Instrumentation.GrpcNetClient

For .NET Framework 4.6.2 and above:

  • OpenTelemetry.Instrumentation.Owin
  • OpenTelemetry.Instrumentation.AspNet

For .NET or .NET Framework 4.7.1 and above:

  • OpenTelemetry.Instrumentation.Quartz

And for all .NET flavors and versions:

  • Npgsql.OpenTelemetry
  • OpenTelemetry.Instrumentation.EntityFrameworkCore
  • OpenTelemetry.Instrumentation.Http
  • OpenTelemetry.Instrumentation.MySqlData
  • OpenTelemetry.Instrumentation.SqlClient
  • OpenTelemetry.Instrumentation.StackExchangeRedis
  • OpenTelemetry.Instrumentation.Wcf

To use the CommonInstrumentations package, first add the package:

dotnet add package Honeycomb.OpenTelemetry.CommonInstrumentations --prerelease

Then, configure the OpenTelemetry SDK to add instrumentation during application start:

using OpenTelemetry.Trace;

var honeycombOptions = builder.Configuration.GetHoneycombOptions();

builder.Services.AddOpenTelemetry().WithTracing(otelBuilder =>
    otelBuilder
        .AddHoneycomb(honeycombOptions)
        .AddCommonInstrumentations();
);

You can add more instrumentation packages during initialization. For example, to add the AWS Lambda Instrumentation package from OpenTelemetry-Contrib, install the package:

dotnet add package OpenTelemetry.Instrumentation.AWSLambda --prerelease

Then, configure the OpenTelemetry SDK to add AWS instrumentation during application start:

using OpenTelemetry;

//...

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddHoneycomb()
    .AddAWSLambdaConfigurations()
    .Build();
Note
Redis instrumentation requires access to the IConnection object to setup tracing. This can either be provided as part of the AddHoneycomb call by setting the RedisConnection property on HoneycombOptions or it can be automatically retrieved if it is registered in the ASP.NET ServiceCollection object.

Add Custom Instrumentation 

Automatic instrumentation is the easiest way to get started with instrumenting your code. To get additional insight into your system, you should also add custom, or manual, instrumentation where appropriate.

To learn more about custom, or manual, instrumentation, visit the comprehensive set of topics covered by Manual Instrumentation for .NET in OpenTelemetry’s documentation, including the System.Diagnostics API and the OpenTelemetry Shim.

Add Attributes to Spans 

Adding attributes to a currently executing span in a trace can be useful. For example, you may have an application or service that handles users and you want to associate the user with the span when querying your dataset in Honeycomb. To do this, get the current span from the context and set an attribute with the user ID:

using OpenTelemetry.Trace;

//...

var currentSpan = Tracer.CurrentSpan;
currentSpan.SetAttribute("user.id", User.GetUserId())

This configuration will add a user.id attribute to the current span, so you can use the field in WHERE, GROUP BY, or ORDER clauses in the Honeycomb query builder.

Acquire a Tracer 

To create spans, you need to acquire a Tracer.

using OpenTelemetry.Trace;

//...

var tracer = TracerProvider.Default.GetTracer("tracer.name.here");

Then, inject the Tracer instance with ASP.NET Core dependency injection or manage its lifecycle manually.

When you create a Tracer, OpenTelemetry requires you to give it a name as a string. This string is the only required parameter.

When traces are sent to Honeycomb, the name of the Tracer is turned into the library.name field, which can be used to show all spans created from a particular tracer.

In general, pick a name that matches the appropriate scope for your traces. If you have one tracer for each service, then use the service name. If you have multiple tracers that live in different “layers” of your application, then use the name that corresponds to that “layer”.

The library.name field is also used with traces created from instrumentation libraries.

Create New Spans 

To get the full picture of what is happening, you must add manual instrumentation and create custom spans to describe what is happening in your application. To do this, grab your tracer instance and use it to create a span:

using OpenTelemetry.Trace;

//...

using var span = TracerProvider.Default.GetTracer("my-service").StartActiveSpan("expensive-query")
// ... do cool stuff

Add Multi-Span Attributes 

Sometimes you want to add the same attribute to many spans within the same trace. This attribute may include variables calculated during your program, or other useful values for correlation or debugging purposes.

To add this attribute, leverage the OpenTelemetry concept of baggage. Baggage allows you to add a key with a value as an attribute to every subsequent child span of the current application context.

using OpenTelemetry;

...

Baggage.Current.SetBaggage("app.username", name);

Note: Any Baggage attributes that you set in your application will be attached to outgoing network requests as a header. If your service communicates to a third party API, do NOT put sensitive information in the Baggage attributes.

Sending Data to a Collector or Other Endpoint 

You can send data to an OpenTelemetry Collector instance or another endpoint by specifying a different endpoint.

The following example shows how to send data to an OpenTelemetry Collector instance over HTTP:

var options = new HoneycombOptions
{
    ServiceName = "my-app",
    ApiKey = "{apikey}"
    TracesEndpoint = "http://<your-collector-endpoint>/v1/traces"

    // MetricsEndpoint is not required if you are not sending metrics
    MetricsEndpoint = "http://<your-collector-endpoint>/v1/metrics"
}
{
  "Honeycomb": {
    "TracesEndpoint": "http://<your-collector-endpoint>/v1/traces",
    "MetricsEndpoint": "http://<your-collector-endpoint>/v1/metrics"
  }
}

Sampling 

You can enable deterministic sampling by providing a SampleRate. A sample rate of 5 means that one out of every five traces will be sent to Honeycomb.

Note
The value of SampleRate must be a positive integer.
var options = new HoneycombOptions
{
    ServiceName = "my-app",
    ApiKey = "{apikey}"
    SampleRate = 5 // sends 1/5 traces
}
{
  "Honeycomb": {
    "SampleRate": 5
  }
}

If you have multiple services that communicate with each other, it is important that they have the same sampling configuration. Otherwise, each service might make a different sampling decision, resulting in incomplete or broken traces. You can sample using a standalone proxy as an alternative, like Honeycomb Refinery, or when you have more robust sampling needs.

Distributed Trace Propagation 

When a service calls another service, you want to ensure that the relevant trace information is propagated from one service to the other. This allows Honeycomb to connect the two services in a trace.

Distributed tracing enables you to trace and visualize interactions between multiple instrumented services. For example, your users may interact with a front-end API service, which talks to two internal APIs to fulfill their request. In order to have traces connect spans for all these services, it is necessary to propagate trace context between these services, usually by using an HTTP header.

Both the sending and receiving service must use the same propagation format, and both services must be configured to send data to the same Honeycomb environment.

Visualizing Traces Locally 

Honeycomb’s OpenTelemetry Distribution for .NET can create a link to a trace visualization in the Honeycomb UI for local traces. Local visualizations enables a faster feedback cycle when adding, modifying, or verifying instrumentation.

  1. Enable local visualizations through application settings or environment variables:

    In `appsettings.json`, set `EnableLocalVisualizations` to `true`.
    
    ```json
    {
        "Honeycomb": {
            "EnableLocalVisualizations": true
        }
    }
    ```
    
    Set the `HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS` environment variable to `true`.
    
    ```bash
    export HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS=true
    ```
    
  2. Run your application:

    dotnet run
    

    The output displays the name of the root span and a link to Honeycomb that shows its trace. For example:

    Trace for root-span-name
    Honeycomb link: <link to Honeycomb trace>
    
  3. Select the link to view the trace in detail within the Honeycomb UI.

Note
In production, disable local visualization. Local visualization creates additional overhead to create the link to a trace in Honeycomb and print it to the console.

Troubleshooting 

To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.