Splunk Observability Cloud 用 .NET Azure 関数をインストルメンテーションする

スパンを Splunk Observability Cloud にエクスポートするために .NET Azure 関数をインストルメンテーションする方法を学習します。プロセス内の関数と分離された関数の両方がサポートされます。

.NET Azure 関数をインストルメンテーションすることで、関数が実行されるたびにスパンを Splunk Observability Cloud に送信できます。分離されたワーカープロセスとプロセス内関数の両方をインストルメンテーションできます。

テレメトリを Splunk Observability Cloud に送信するために、.NET Azure 関数を OpenTelemetry でインストルメンテーションするには、以下のハイレベルな手順に従ってください:

注: SignalFx C# Azure Function ラッパーは非推奨です。次のメソッドを使用して、Azure 関数をインストルメンテーションします。

環境変数の定義

関数の設定で必要な環境変数を設定します。

  1. 関数アプリで関数を選択します。

  2. Settings にアクセスし、次に Configuration にアクセスします。

  3. New application setting を選択し、以下の設定を追加します:

    Name

    SPLUNK_ACCESS_TOKEN

    Splunk アクセストークン。アクセストークンを取得するには、「Splunk Observability Cloud を使用したユーザー API アクセストークンの取得と管理」を参照してください。

    SPLUNK_REALM

    Splunk Observability Cloud のレルム(us0 など)。Splunk レルムを見つけるには、「Note about realms」を参照してください。

  4. その他必要な設定を追加します。

NuGetを使って必要なライブラリを追加する

Visual StudioのNuGetを使って以下のライブラリを追加します:

分離されたワーカープロセス機能
  1. Include prerelease 設定をアクティブ化します。

  2. 以下のライブラリの最新版をインストールします:

.NET 6インプロセス機能
  1. Include prerelease 設定をアクティブ化します。

  2. 以下のライブラリの指定されたバージョンをインストールします:

注: 実行時の依存関係があるため、プロセス内関数のインストルメンテーションでは、指定されたバージョンのみが動作することが保証されています。
.NET 8インプロセス機能
  1. Include prerelease 設定をアクティブ化します。

  2. 以下のライブラリの指定されたバージョンをインストールします:

注: 実行時の依存関係があるため、プロセス内関数のインストルメンテーションでは、指定されたバージョンのみが動作することが保証されています。

コード内でOpenTelemetryを初期化する

依存関係を追加したら、関数内で OpenTelemetry を初期化します。

分離されたワーカープロセス機能

Program.csファイルにスタートアップ初期化を追加します:

CSHARP
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.ResourceDetectors.Azure;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System.Diagnostics;

// Get environment variables from function configuration
// You need a valid Splunk Observability Cloud access token and realm
var serviceName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") ?? "Unknown";
var accessToken = Environment.GetEnvironmentVariable("SPLUNK_ACCESS_TOKEN")?.Trim();
var realm = Environment.GetEnvironmentVariable("SPLUNK_REALM")?.Trim();

ArgumentNullException.ThrowIfNull(accessToken, "SPLUNK_ACCESS_TOKEN");
ArgumentNullException.ThrowIfNull(realm, "SPLUNK_REALM");

var tp = Sdk.CreateTracerProviderBuilder()
   // 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;
   })
   // Use AddSource to add your custom DiagnosticSource source names
   //.AddSource("My.Source.Name")
   // Creates root spans for function executions
   .AddSource("Microsoft.Azure.Functions.Worker")
   .SetSampler(new AlwaysOnSampler())
   .ConfigureResource(configure => configure
      .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
      // See https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/main/src/OpenTelemetry.ResourceDetectors.Azure
      // for other types of Azure detectors
      .AddDetector(new AppServiceResourceDetector()))
   .AddOtlpExporter(opts =>
   {
      opts.Endpoint = new Uri($"https://ingest.{realm}.signalfx.com/v2/trace/otlp");
      opts.Protocol = OtlpExportProtocol.HttpProtobuf;
      opts.Headers = $"X-SF-TOKEN={accessToken}";
   })
   .Build();

var host = new HostBuilder()
   .ConfigureFunctionsWorkerDefaults()
   .ConfigureServices(services => services.AddSingleton(tp))
   .Build();

host.Run();
注: 分離されたワーカープロセス関数をインストルメンテーションする場合、起動時の初期化やパラメータを他の関数にカプセル化することができます。
.NET 6インプロセス機能

スタートアップ関数を定義し、その関数でアセンブリを装飾します。スタートアップ関数は、Azure.Functions.Extensions パッケージを使用して有用なメタデータを収集します。

CSHARP
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Collections.Generic;

// Decorate assembly with startup function
[assembly: FunctionsStartup(typeof(OtelManualExample.Startup))]

namespace OtelManualExample
{
   public class Startup : FunctionsStartup
   {
      public override void Configure(IFunctionsHostBuilder builder)
      {
         // Get environment variables from function configuration
         // You need a valid Splunk Observability Cloud access token and realm
         var serviceName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") ?? "Unknown";
         var accessToken = Environment.GetEnvironmentVariable("SPLUNK_ACCESS_TOKEN")?.Trim();
         var realm = Environment.GetEnvironmentVariable("SPLUNK_REALM")?.Trim();

         ArgumentNullException.ThrowIfNull(accessToken, "SPLUNK_ACCESS_TOKEN");
         ArgumentNullException.ThrowIfNull(realm, "SPLUNK_REALM");

         var tp = Sdk.CreateTracerProviderBuilder()
            // 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.Filter = req => Activity.Current?.Parent != null)
            .AddAspNetCoreInstrumentation()
            // Use AddSource to add your custom DiagnosticSource source names
            //.AddSource("My.Source.Name")
            .SetSampler(new AlwaysOnSampler())
            // Add resource attributes to all spans
            .SetResourceBuilder(
               ResourceBuilder.CreateDefault()
               .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
               .AddAttributes(new Dictionary<string, object>() {
                  { "faas.instance", Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") }
               }))
            .AddOtlpExporter(opts =>
            {
               opts.Endpoint = new Uri($"https://ingest.{realm}.signalfx.com/v2/trace/otlp");
               opts.Protocol = OtlpExportProtocol.HttpProtobuf;
               opts.Headers = $"X-SF-TOKEN={accessToken}";
            })
            .Build();

         builder.Services.AddSingleton(tp);
      }
   }
}
.NET 8インプロセス機能

スタートアップ関数を定義し、その関数でアセンブリを装飾します。スタートアップ関数は、Azure.Functions.Extensions パッケージを使用して有用なメタデータを収集します。

CSHARP
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Collections.Generic;

// Decorate assembly with startup function
[assembly: FunctionsStartup(typeof(OtelManualExample.Startup))]

namespace OtelManualExample
{
   public class Startup : FunctionsStartup
   {
      public override void Configure(IFunctionsHostBuilder builder)
      {
         // Get environment variables from function configuration
         // You need a valid Splunk Observability Cloud access token and realm
         var serviceName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") ?? "Unknown";
         var accessToken = Environment.GetEnvironmentVariable("SPLUNK_ACCESS_TOKEN")?.Trim();
         var realm = Environment.GetEnvironmentVariable("SPLUNK_REALM")?.Trim();

         ArgumentNullException.ThrowIfNull(accessToken, "SPLUNK_ACCESS_TOKEN");
         ArgumentNullException.ThrowIfNull(realm, "SPLUNK_REALM");

         var tp = Sdk.CreateTracerProviderBuilder()
            // 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.FilterHttpRequestMessage = req => Activity.Current?.Parent != null;
                opts.FilterHttpWebRequest = req => Activity.Current?.Parent != null;
            })
            .AddAspNetCoreInstrumentation()
            // Use AddSource to add your custom DiagnosticSource source names
            //.AddSource("My.Source.Name")
            .SetSampler(new AlwaysOnSampler())
            // Add resource attributes to all spans
            .SetResourceBuilder(
               ResourceBuilder.CreateDefault()
               .AddService(serviceName: serviceName, serviceVersion: "1.0.0")
               .AddAzureAppServiceDetector()
               .AddAttributes(new Dictionary<string, object>() {
                  { "faas.instance", Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID") }
               }))
            .AddOtlpExporter(opts =>
            {
               opts.Endpoint = new Uri($"https://ingest.{realm}.signalfx.com/v2/trace/otlp");
               opts.Protocol = OtlpExportProtocol.HttpProtobuf;
               opts.Headers = $"X-SF-TOKEN={accessToken}";
            })
            .Build();

         builder.Services.AddSingleton(tp);
      }
   }
}

スパンを送信するコードをインストルメンテーションする

次に、OpenTelemetry を使用してコードをインストルメンテーションします。コードをインストルメンテーションする出発点として、以下の例を使ってください。Azure 関数に環境変数を追加する手順については、Microsoft Azure ドキュメント(https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings)を参照してください。

分離されたワーカープロセス機能

次の例では、startヘルパー関数とstopヘルパー関数を使ったインストルメンテーションの方法を示しています:

CSHARP
using System.Diagnostics;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

namespace OtelManualIsolatedExample
{
   public class ExampleFunction
   {
      private readonly ILogger _logger;

      public ExampleFunction(ILoggerFactory loggerFactory)
      {
            _logger = loggerFactory.CreateLogger<ExampleFunction>();
      }
// Define helper functions for manual instrumentation
      public static ActivitySource ManualInstrumentationSource = new ActivitySource("manualInstrumentation");
      public static Activity? StartActivity(HttpRequestData req, FunctionContext fc)
      {
// Retrieve resource attributes
            var answer = ManualInstrumentationSource.StartActivity(req.Method.ToUpper() + " " + req.Url.AbsolutePath, ActivityKind.Server);
            answer?.AddTag("http.url", req.Url);
            answer?.AddTag("faas.invocation_id", fc.InvocationId.ToString());
            answer?.AddTag("faas.name", Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") + "/" + fc.FunctionDefinition.Name);
            return answer;
      }
      public static HttpResponseData FinishActivity(HttpResponseData response, Activity? activity)
      {
            activity?.AddTag("http.status_code", ((int)response.StatusCode));
            return response;
      }

      [Function("ExampleFunction")]
// Add the FunctionContext parameter to capture per-invocation information
      public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, FunctionContext fc)
      {
            using (var activity = StartActivity(req, fc))
            {
               var response = req.CreateResponse(HttpStatusCode.OK);
               response.Headers.Add("Content-Type", "text/plain; charset=utf-8");

               response.WriteString("The current time is " + DateTime.Now.ToLongTimeString());

               return FinishActivity(response, activity);
            }
      }
   }
}
インプロセス機能

次の例は、faas 属性を取得する方法を示しています:

CSHARP
public static class ExampleFunction
{
   [FunctionName("ExampleFunction")]
// Add the ExecutionContext parameter to capture per-invocation information
   public static async Task<IActionResult> Run(
         [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
         ILogger log, ExecutionContext context)
   {
// You can also factor this out into a helper method to use across all functions
         Activity.Current.AddTag("faas.invocation_id", context.InvocationId.ToString());
         Activity.Current.AddTag("faas.name", Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") + "/" + context.FunctionName);

         string responseMessage = "The current time is " + DateTime.Now.ToLongTimeString();
         return new OkObjectResult(responseMessage);
   }
}

データが入力されていることを確認する

関数を実行し、そのスパンを Splunk APM で検索します。詳細については、「トレース内でのスパンの表示およびフィルタリング」を参照してください。

分離されたワーカープロセス機能

次の画像は、分離されたワーカープロセス関数によって送信されたスパンを示しています。faas タグに注意してください。

孤立したワーカープロセス関数からのスパン詳細
インプロセス機能

次の画像は、インプロセス関数から送信されたスパンを示しています。faas タグに注意してください。

インプロセス関数からのスパンの詳細

トラブルシューティング

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 community #observability Slack channel to communicate with customers, partners, and Splunk employees worldwide.