銀の光と碧い空

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

OpenTelemetry .NETを理解する (3) デバッグログを出す

OpenTelemetry .NET で計測していると、正しく動作しているか確認したいときがあります。OpenTelemetry .NETにはこのようなときのために、自己診断機能が用意されています。

opentelemetry-dotnet/README.md at main · open-telemetry/opentelemetry-dotnet · GitHub

このドキュメントにあるとおり、計装コード側を変更する必要はなく、カレントディレクトリに以下の形式のOTEL_DIAGNOSTICS.jsonという名前のファイルを配置するだけです。

{
    "LogDirectory": ".",
    "FileSize": 1024,
    "LogLevel": "Error"
}

設定パラメータもドキュメントに説明されています。

  • LogDirectory にログファイルを出力するディレクトリを指定します。ここではカレントディレクトリに出力します。
  • FileSize にログファイルのサイズをKiB単位で指定します。1~128MiBの間で指定でき、ここでは1MiBを指定しています。
  • LogLevel に指定したレベル以上のログが記録されます。EventLevel列挙型の値を指定できます。

出力されるログファイルは{実行ファイルの名前}.{プロセスID}.logというフォーマットになります。また、FileSizeで指定したサイズになるよう、ログ出力が少ないときは末尾がNULで埋められています。

f:id:tanaka733:20220313165936p:plain

f:id:tanaka733:20220313170043p:plain

OpenTelemetry .NET ハンズオンを行いました

C# Tokyoでの企画でOpenTelemetryの初心者向けハンズオンを行いました。

csharp-tokyo.connpass.com

OpenTelemetry .NETについての発表資料はこちらです。

www.slideshare.net

ハンズオン課題はこちらのリポジトリのhandson1フォルダに配置しています。

github.com

当日参加者の方にはすぐに使えるNew Relicのアカウントを提供しましたが、もし試す場合はこちらの記事を参考にNew Relicの無償アカウントをセットアップするか、別のバックエンドに送信できます。

tech.tanaka733.net

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はよく使われるフレームワークであれば、まず使い始めるのは非常にかんたんになっています。