銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

OpenTelemetry .NETを理解する (2) ASP.NET CoreにOpenTelemetryをまずいれてみる

今回からは実際にOpenTelemetry .NETを使って計測してみることにします。まずは、ASP.NET Core (.NET 6)を題材にします。

OpenTelemetryのドキュメントと重なる部分も多いのであわせて参照してみてください。

opentelemetry.io

最初の計装

まずは、計測対象のASP.NET Coreプロジェクトを作ります。dotnet new web で作れますが、.NET 6だと1ファイルで完結する形でかんたんなWebアプリが作れるので以下のコードをスタートにします。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
    
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
       new WeatherForecast
       (
           DateTime.Now.AddDays(index),
           Random.Shared.Next(-20, 55),
           summaries[Random.Shared.Next(summaries.Length)]
       ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

ここから計測コードを実装する計装作業を行います。まず、必要なライブラリはNuGetで提供されているので、以下の2つをまず追加します。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc8" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc8" />
  </ItemGroup>

またこれらのパッケージは、OpenTelemetryOpenTelemetry.Apiに依存しています。この2つは計装のためのOpenのAPIとSDKの言語ごとの実装であり、計装するために最低限必要なライブラリという位置づけになっています。APIとSDKについては、ライブラリ開発者がOpenTelemetry .NETを使うユースケースで説明しようと思っています。

OpenTelemetry.Extensions.HostingとOpenTelemetry.Instrumentation.AspNetCoreは計装の手間をへらすために、よく使われるライブラリ(ここではASP.NET Core)を計装する処理を提供します。前回の記事の自動計装(automatic instrumentation)と呼ばれる機能です。APIとSDKは安定版ですが、自動計装部分についてはRC版となっています。

実際の計装コードは次のように記述します。先程のコードの先頭部分に次のように追加します。

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

var tags = new Dictionary<string, object>
        {
            { "Environment", "Production" },
            { "Level", 99 },
        };

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
    );

SetResourceBuilderメソッドの中の「Hello-Otel」はこのアプリケーションを識別するための名前をつけます。またアプリケーションを分類するためのタグを追加しています。これだけでASP.NET CoreアプリケーションをOpenTelemetryの標準的な機能で計測することができます。ただ、これだけだと計測したデータをどこにも送信していません。まずはコンソールに出力して動作確認することにします。

ConsoleExporterの追加

Exporterは出力先ごとにNuGetライブラリが提供されています。Consoleに出力する場合、以下のライブラリを追加します。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.1.0" />
  </ItemGroup>

Exporterを設定するのは、先程のbuilder.Services.AddOpenTelemetryTracingメソッドの中で、次のように設定します。

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddConsoleExporter()
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
    );

これだけで完了です。実際に実行してエンドポイントにアクセスすると次のようなデータがコンソールに出力されるはずです。

Activity.Id:          00-016a34b6053f5d51737b3419a41e4604-c08de677265e1e72-01
Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore
Activity.DisplayName: WeatherForecast
Activity.Kind:        Server
Activity.StartTime:   2022-01-06T06:33:00.9914510Z
Activity.Duration:    00:00:00.0496200
Activity.TagObjects:
    http.host: localhost:7174
    http.method: GET
    http.target: /WeatherForecast
    http.url: https://localhost:7174/WeatherForecast
    http.user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
    http.route: WeatherForecast
    http.status_code: 200
    otel.status_code: UNSET
Resource associated with Activity:
    Environment: Production
    Level: 99
    service.name: Hello-Otel
    service.instance.id: 509603d5-39e6-4395-9c71-884e27f61072

実行された処理がトレースとして計測されたものです。トレースを識別するためのIdや名前、時刻、経過時間、タグなどが記録されていることがわかります。また、「Resource associated with Activity」ではSetResourceBuilderで設定したアプリの名前やタグも確認できます。

OTLPExporterでNew Relicに送信する

ほとんどのケースではコンソールではなく、別のどこかに送信し、最終的には記録・利用のためのオブザーバビリティバックエンドに送信することになります。前回の記事の「OpenTelemetery で計測するために必要なもの」の図ではコレクターを経由して送信しています。実用的にはコレクターを経由するケースが多いと思うのですが、ひとまず検証するという意味ではアプリケーションのOpenTelemetryコードから直接オブザーバビリティバックエンドに送信することができます。その場合、バックエンドがOpenTelemetry .NET向けにExporterライブラリを提供していれば利用できます。特に、OTLP形式のエンドポイントをサポートしているバックエンドであればOpenTelemetry .NETが提供しているOTLPExporterが利用できます。そして、New RelicはOTLP形式のエンドポイントをサポートしているので試しに利用できます。

New Relic側の準備については公式ブログを参照してください。具体的にはライセンスキーとエンドポイントをメモしておいてください。

newrelic.com

それは実装してみます。まずはOTLPExporterのライブラリを追加します。先程のConsoleExpoterは不要ですが、残しておいても問題はありません。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.1.0" />
  </ItemGroup>

AddConsoleExporterの代わりにAddOtlpExporterを呼びます。そして、以下のようにエンドポイントと認証のためのヘッダーを設定します。ここではエンドポイントをOTEL_ENDPOINTという名前の、認証ヘッダーのキーがapi-keyで値がOTEL_API_KEYという名前の、環境変数に設定しています。

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
        .AddOtlpExporter(options => 
        {
            options.Endpoint = new Uri(Environment.GetEnvironmentVariable("OTEL_ENDPOINT"));
            options.Headers = $"api-key={Environment.GetEnvironmentVariable("OTEL_API_KEY")}";
        })
    );

エンドポイントや認証のために利用するヘッダーのキーの名前はOTLPをサポートしているバックエンドごとに提供されています。なのでほぼ同じ書き方で設定できるはずです。また、その他のExpoterも設定はこのような書き方でできるものが多いです。これでOTLPフォーマットで送信する設定は完了です。再度アプリケーションを起動し、アクセスすると送信されるはずです。

送信されたデータをどのように表示するかはオブザーバビリティバックエンドの機能に依存します。New Relicの場合、送信するだけでアプリケーションごとに以下のようなダッシュボードに表示されます。

f:id:tanaka733:20220119225731p:plain

また、先程コンソールに出力したようなトレースの詳細は分散トレース(Distributed Tracing)のUIで確認できます。

f:id:tanaka733:20220119231437p:plain

このようにOpenTelemetry .NETはよく使われるフレームワークであれば、まず使い始めるのは非常にかんたんになっています。