Docs » Supported integrations in Splunk Observability Cloud » Instrument back-end applications to send spans to Splunk APM » Instrument .NET applications for Splunk Observability Cloud (OpenTelemetry) » Instrument .NET Azure Web App for Splunk Observability Cloud

Instrument .NET Azure Web App for Splunk Observability Cloud 🔗

You can instrument applications or services running on Azure Web App Service using the OpenTelemetry .NET SDK. Follow these instructions to get started.

Define the environment variables 🔗

Set the required environment variables for your application.

  1. Select your application in Azure Web App.

  2. Go to Settings, Configuration.

  3. Select New application setting to add the following settings:

    Name

    Value

    SPLUNK_ACCESS_TOKEN

    Your Splunk access token. To obtain an access token, see Retrieve and manage user API access tokens using Splunk Observability Cloud.

    SPLUNK_REALM

    Your Splunk Observability Cloud realm, for example us0. To find your realm, see Note about realms.

    SPLUNK_TRACE_RESPONSE_HEADER_ENABLED

    Whether to turn on the integration with Splunk RUM. The default value is false.

  4. Add any other settings you might need. See Configure the Splunk Distribution of OpenTelemetry .NET.

Add the required libraries using NuGet 🔗

Add the following libraries using the NuGet package manager in Visual Studio.

Isolated worker process function 🔗

  1. Activate the Include prerelease setting.

  2. Install the latest version of the following libraries:

Initialize OpenTelemetry in the code 🔗

After adding the dependencies, create an OpenTelemetry helper for your application:

using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.ResourceDetectors.Azure;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Diagnostics;

namespace <YourNamespaceHere>.Extensions;

public static class SplunkOpenTelemetry
{
    private static readonly string AccessToken;
    private static readonly string Realm;

    static SplunkOpenTelemetry()
    {
        // Get environment variables from function configuration
        // You need a valid Splunk Observability Cloud access token and realm
        AccessToken = Environment.GetEnvironmentVariable("SPLUNK_ACCESS_TOKEN")?.Trim()
            ?? throw new ArgumentNullException("SPLUNK_ACCESS_TOKEN");

        Realm = Environment.GetEnvironmentVariable("SPLUNK_REALM")?.Trim()
            ?? throw new ArgumentNullException("SPLUNK_REALM");
    }

    public static WebApplicationBuilder AddSplunkOpenTelemetry(this WebApplicationBuilder builder)
    {
        var serviceName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") ?? "Unknown";
        var enableTraceResponseHeaderValue = Environment.GetEnvironmentVariable("SPLUNK_TRACE_RESPONSE_HEADER_ENABLED")?.Trim();

        // See https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/main/src/OpenTelemetry.ResourceDetectors.Azure
        // for other types of Azure detectors
        var resourceDetector = new AppServiceResourceDetector();

        builder.Services.AddOpenTelemetry()
            .WithTracing(t => t
                // Use Add[instrumentation-name]Instrumentation to instrument missing services
                // Use Nuget to find different instrumentation libraries
                .AddHttpClientInstrumentation(opts =>
                {
                    // This filter prevents background (parent-less) http client activity
                    opts.FilterHttpWebRequest = req => Activity.Current?.Parent != null;
                    opts.FilterHttpRequestMessage = req => Activity.Current?.Parent != null;
                })
                .AddAspNetCoreInstrumentation(opts =>
                {
                    // Enables Splunk RUM integration when configuration contains SPLUNK_TRACE_RESPONSE_HEADER_ENABLED=True
                    if (bool.TryParse(enableTraceResponseHeaderValue, out bool isEnabled) && isEnabled)
                    {
                        opts.EnrichWithHttpRequest = (activity, request) =>
                        {
                            var response = request.HttpContext.Response;
                            ServerTimingHeader.SetHeaders(activity, response.Headers, (headers, key, value) =>
                            {
                                headers.TryAdd(key, value);
                            });
                        };
                    }
                })
                // Use AddSource to add your custom DiagnosticSource source names
                //.AddSource("My.Source.Name")
                .SetSampler(new AlwaysOnSampler())
                .ConfigureResource(cfg => cfg
                    .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
                    .AddDetector(resourceDetector))
                .AddOtlpExporter(opts =>
                {
                    opts.Endpoint = new Uri($"https://ingest.{Realm}.signalfx.com/v2/trace/otlp");
                    opts.Protocol = OtlpExportProtocol.HttpProtobuf;
                    opts.Headers = $"X-SF-TOKEN={AccessToken}";
                }))
            .WithMetrics(m => m
                // Use Add[instrumentation-name]Instrumentation to instrument missing services
                // Use Nuget to find different instrumentation libraries
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddRuntimeInstrumentation()
                .AddProcessInstrumentation()
                .ConfigureResource(cfg => cfg
                    .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
                    .AddDetector(resourceDetector))
                .AddOtlpExporter(opts =>
                {
                    opts.Endpoint = new Uri($"https://ingest.{Realm}.signalfx.com/v2/datapoint/otlp");
                    opts.Headers = $"X-SF-TOKEN={AccessToken}";
                }));

        return builder;
    }

    private static class ServerTimingHeader
    {
        private const string Key = "Server-Timing";
        private const string ExposeHeadersHeaderName = "Access-Control-Expose-Headers";

        public static void SetHeaders<T>(Activity activity, T carrier, Action<T, string, string> setter)
        {
            setter(carrier, Key, ToHeaderValue(activity));
            setter(carrier, ExposeHeadersHeaderName, Key);
        }

        private static string ToHeaderValue(Activity activity)
        {
            var sampled = ((int)activity.Context.TraceFlags).ToString("D2");
            return $"traceparent;desc=\"00-{activity.TraceId}-{activity.SpanId}-{sampled}\"";
        }
    }
}

Use the helper you created in the Program.cs file:

var builder = WebApplication.CreateBuilder(args);
var app = builder
    .AddSplunkOpenTelemetry()
    .Build()

Check that data is coming in 🔗

Run your function and search for spans in Splunk APM. See View and filter for spans within a trace for more information.

Troubleshooting 🔗

If you are a Splunk Observability Cloud customer and are not able to see your data in Splunk Observability Cloud, you can get help in the following ways.

Available to Splunk Observability Cloud customers

Available to prospective customers and free trial users

  • Ask a question and get answers through community support at Splunk Answers .

  • Join the Splunk #observability user group Slack channel to communicate with customers, partners, and Splunk employees worldwide. To join, see Chat groups in the Get Started with Splunk Community manual.

To learn about even more support options, see Splunk Customer Success .