計装
計装とは、あなた自身がアプリにオブザーバビリティコードを追加する行為です。
アプリを計装する場合、あなたが使用する言語に対応したOpenTelemetry SDKを使用する必要があります。 次に、SDKを使用してOpenTelemetryを初期化し、APIを使用してコードを計装します。 これにより、アプリ本体だけでなく、計装が含まれるライブラリからもテレメトリーが出力されるようになります。
ライブラリを計装する場合、使用する言語に対応したOpenTelemetry APIパッケージのみをインストールしてます。 ライブラリ自体はテレメトリーを出力しません。 ライブラリの計装についての詳細は、ライブラリを参照してください。
OpenTelemetry APIとSDKについての詳細は、仕様を参照してください。
用語に関する注意
.NET は OpenTelemetry をサポートする他の言語やランタイムとは異なります。
トレース API は System.Diagnostics API によって実装されており、ActivitySource や Activity といった既存のコンストラクトを再利用して、内部的に OpenTelemetry に準拠するようになっています。
しかし、.NET 開発者がアプリケーションを計装するために知っておく必要がある OpenTelemetry API と用語の部分があり、ここでは System.Diagnostics API とともにそれらを取り上げます。
System.Diagnostics API のかわりに OpenTelemetry API を使用したい場合は、トレース用の OpenTelemetry API Shim のドキュメントを参照してください。
サンプルアプリの準備
このページでは、手動計装について学ぶために、はじめにのサンプルアプリを修正したバージョンを使用します。
サンプルアプリを使う必要はありません。 自分のアプリやライブラリを計装したい場合は、ここに記載された手順を自分のコードに合わせて適用してください。
前提条件
- .NET SDK 6+
HTTP サーバーの作成と起動
まず、dotnet-otel-example という新しいディレクトリに環境をセットアップします。
そのディレクトリ内で、次のコマンドを実行してください。
dotnet new web
ライブラリの計装とスタンドアロンアプリの計装の違いを明確にするために、サイコロを振る処理をライブラリファイルに分離し、アプリファイルから依存関係としてインポートします。
Dice.cs という名前のライブラリファイルを作成し、次のコードを追加してください。
/*Dice.cs*/
public class Dice
{
private int min;
private int max;
public Dice(int min, int max)
{
this.min = min;
this.max = max;
}
public List<int> rollTheDice(int rolls)
{
List<int> results = new List<int>();
for (int i = 0; i < rolls; i++)
{
results.Add(rollOnce());
}
return results;
}
private int rollOnce()
{
return Random.Shared.Next(min, max + 1);
}
}
アプリファイル DiceController.cs を作成し、次のコードを追加してください。
/*DiceController.cs*/
using Microsoft.AspNetCore.Mvc;
using System.Net;
public class DiceController : ControllerBase
{
private ILogger<DiceController> logger;
public DiceController(ILogger<DiceController> logger)
{
this.logger = logger;
}
[HttpGet("/rolldice")]
public List<int> RollDice(string player, int? rolls)
{
if(!rolls.HasValue)
{
logger.LogError("Missing rolls parameter");
throw new HttpRequestException("Missing rolls parameter", null, HttpStatusCode.BadRequest);
}
var result = new Dice(1, 6).rollTheDice(rolls.Value);
if (string.IsNullOrEmpty(player))
{
logger.LogInformation("Anonymous player is rolling the dice: {result}", result);
}
else
{
logger.LogInformation("{player} is rolling the dice: {result}", player, result);
}
return result;
}
}
Program.cs ファイルの内容を次のコードに置き換えてください。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Properties サブディレクトリ内の launchSettings.json の内容を次の内容に置き換えてください。
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:8080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
動作確認のために、次のコマンドでアプリケーションを実行し、ウェブブラウザで http://localhost:8080/rolldice?rolls=12 を開いてください。
dotnet run
ブラウザウィンドウに12個の数字のリストが表示されるはずです。たとえば次のようになります。
[5,6,5,3,6,1,2,5,4,4,2,4]
手動計装のセットアップ
依存関係
次の OpenTelemetry NuGet パッケージをインストールしてください。
OpenTelemetry.Exporter.Console
OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting
ASP.NET Core ベースのアプリケーションの場合は、AspNetCore 計装パッケージもインストールしてください。
OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
SDK の初期化
ライブラリを計装する場合、SDK を初期化する必要はありません。
OpenTelemetry SDK のインスタンスをアプリケーションのできるだけ早い段階で設定することが重要です。
サンプルアプリのような ASP.NET Core アプリで OpenTelemetry SDK を初期化するには、Program.cs の内容を次のコードに更新してください。
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
// この名前は設定ファイルや定数ファイルなどから取得するのが理想的です。
var serviceName = "dice-server";
var serviceVersion = "1.0.0";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceName: serviceName,
serviceVersion: serviceVersion))
.WithTracing(tracing => tracing
.AddSource(serviceName)
.AddAspNetCoreInstrumentation()
.AddConsoleExporter())
.WithMetrics(metrics => metrics
.AddMeter(serviceName)
.AddConsoleExporter());
builder.Logging.AddOpenTelemetry(options => options
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
serviceName: serviceName,
serviceVersion: serviceVersion))
.AddConsoleExporter());
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
コンソールアプリで OpenTelemetry SDK を初期化する場合は、プログラムの冒頭、重要なスタートアップ処理の際に次のコードを追加してください。
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
//...
var serviceName = "MyServiceName";
var serviceVersion = "1.0.0";
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(serviceName)
.ConfigureResource(resource =>
resource.AddService(
serviceName: serviceName,
serviceVersion: serviceVersion))
.AddConsoleExporter()
.Build();
var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(serviceName)
.AddConsoleExporter()
.Build();
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(logging =>
{
logging.AddConsoleExporter();
});
});
//...
tracerProvider.Dispose();
meterProvider.Dispose();
loggerFactory.Dispose();
デバッグとローカル開発のために、このサンプルではテレメトリーをコンソールにエクスポートしています。 手動計装のセットアップが完了したら、アプリのテレメトリーデータを1つ以上のテレメトリーバックエンドにエクスポートするために、適切なエクスポーターを設定する必要があります。
このサンプルでは、サービスの論理名を保持する必須の SDK デフォルト属性 service.name と、オプションですが強く推奨される属性 service.version(サービスの API または実装のバージョンを保持する)も設定しています。
リソース属性を設定する別の方法もあります。
詳細については、リソースを参照してください。
コードを検証するために、アプリをビルドして実行してください。
dotnet build
dotnet run
トレース
トレースの初期化
ライブラリを計装する場合、TracerProvider を初期化する必要はありません。
アプリでトレースを有効にするには、Tracer を作成するための初期化済みの TracerProvider が必要です。
TracerProvider が作成されていない場合、トレース用の OpenTelemetry API は no-op 実装を使用し、データを生成しません。
SDK の初期化の手順に従った場合は、TracerProvider はすでにセットアップされています。
ActivitySource のセットアップに進んでください。
ActivitySource のセットアップ
アプリケーションで手動トレースコードを書く場所には、ActivitySource を設定する必要があります。
これにより Activity 要素を使用してオペレーションをトレースできるようになります。
通常、計装対象のアプリ/サービスごとに ActivitySource を一度定義することが推奨されますが、シナリオに合わせて複数の ActivitySource をインスタンス化することもできます。
サンプルアプリの場合、ActivitySource への参照を保持するカスタム型として新しいファイル Instrumentation.cs を作成します。
using System.Diagnostics;
/// <summary>
/// ActivitySource の参照を保持するにはカスタム型を使用することが推奨されます。
/// これにより、DI コンテナ内の他のコンポーネントとの型の衝突を回避できます。
/// </summary>
public class Instrumentation : IDisposable
{
internal const string ActivitySourceName = "dice-server";
internal const string ActivitySourceVersion = "1.0.0";
public Instrumentation()
{
this.ActivitySource = new ActivitySource(ActivitySourceName, ActivitySourceVersion);
}
public ActivitySource ActivitySource { get; }
public void Dispose()
{
this.ActivitySource.Dispose();
}
}
次に、Instrumentation オブジェクトを依存性注入として追加するために Program.cs を更新します。
//...
// Instrumentation クラスを DI コンテナにシングルトンとして登録します。
builder.Services.AddSingleton<Instrumentation>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
アプリケーションファイル DiceController.cs でその activitySource インスタンスを参照し、同じ activitySource インスタンスをライブラリファイル Dice.cs にも渡します。
/*DiceController.cs*/
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Net;
public class DiceController : ControllerBase
{
private ILogger<DiceController> logger;
private ActivitySource activitySource;
public DiceController(ILogger<DiceController> logger, Instrumentation instrumentation)
{
this.logger = logger;
this.activitySource = instrumentation.ActivitySource;
}
[HttpGet("/rolldice")]
public List<int> RollDice(string player, int? rolls)
{
List<int> result = new List<int>();
if (!rolls.HasValue)
{
logger.LogError("Missing rolls parameter");
throw new HttpRequestException("Missing rolls parameter", null, HttpStatusCode.BadRequest);
}
result = new Dice(1, 6, activitySource).rollTheDice(rolls.Value);
if (string.IsNullOrEmpty(player))
{
logger.LogInformation("Anonymous player is rolling the dice: {result}", result);
}
else
{
logger.LogInformation("{player} is rolling the dice: {result}", player, result);
}
return result;
}
}
/*Dice.cs*/
using System.Diagnostics;
public class Dice
{
public ActivitySource activitySource;
private int min;
private int max;
public Dice(int min, int max, ActivitySource activitySource)
{
this.min = min;
this.max = max;
this.activitySource = activitySource;
}
//...
}
Activity の作成
activitySource が初期化されたので、Activity を作成できます。
以下のコードは Activity を作成する方法を示しています。
public List<int> rollTheDice(int rolls)
{
List<int> results = new List<int>();
// Activity の作成は、独立して測定する価値のあるオペレーションを実行するときのみ推奨されます。
// Activity が多すぎると、Jaeger などのツールでの可視化が難しくなります。
using (var myActivity = activitySource.StartActivity("rollTheDice"))
{
for (int i = 0; i < rolls; i++)
{
results.Add(rollOnce());
}
return results;
}
}
ここまでサンプルアプリの手順に従っている場合は、上記のコードをライブラリファイル Dice.cs にコピーしてください。
これで、アプリから出力される Activity/スパンを確認できるはずです。
次のようにアプリを起動し、ブラウザまたは curl で http://localhost:8080/rolldice?rolls=12 にリクエストを送信してください。
dotnet run
しばらくすると、ConsoleExporter によってコンソールにスパンが出力されるはずです。
たとえば次のようになります。
Activity.TraceId: 841d70616c883db82b4ae4e11c728636
Activity.SpanId: 9edfe4d69b0d6d8b
Activity.TraceFlags: Recorded
Activity.ParentSpanId: 39fcd105cf958377
Activity.ActivitySourceName: dice-server
Activity.DisplayName: rollTheDice
Activity.Kind: Internal
Activity.StartTime: 2024-04-10T15:24:00.3620354Z
Activity.Duration: 00:00:00.0144329
Resource associated with Activity:
service.name: dice-server
service.version: 1.0.0
service.instance.id: 7a7a134f-3178-4ac6-9625-96df77cff8b4
telemetry.sdk.name: opentelemetry
telemetry.sdk.language: dotnet
telemetry.sdk.version: 1.7.0
ネストされた Activity の作成
ネストされたスパンを使うと、ネストされた性質の処理を追跡できます。
たとえば、以下の rollOnce() 関数はネストされたオペレーションを表します。
次のサンプルは rollOnce() を追跡するネストされたスパンを作成します。
private int rollOnce()
{
using (var childActivity = activitySource.StartActivity("rollOnce"))
{
int result;
result = Random.Shared.Next(min, max + 1);
return result;
}
}
トレース可視化ツールでスパンを表示すると、rollOnce の childActivity は rollTheDice Activity の下にネストされたオペレーションとして追跡されます。
現在の Activity の取得
プログラムの特定の時点で現在の Activity/スパンに対して何かを行うことが役立つ場合があります。
var activity = Activity.Current;
Activity タグ
タグ(属性に相当するもの)を使用すると、Activity にキー/バリューペアを付加して、追跡中の現在のオペレーションに関するより多くの情報を持たせることができます。
private int rollOnce()
{
using (var childActivity = activitySource.StartActivity("rollOnce"))
{
int result;
result = Random.Shared.Next(min, max + 1);
childActivity?.SetTag("dicelib.rolled", result);
return result;
}
}
Activity へのイベントの追加
スパンには、名前付きイベント(スパンイベントと呼ばれる)でアノテーションを付けることができます。 スパンイベントは0個以上のスパン属性を持つことができ、それぞれがタイムスタンプと自動的にペアになるキー:バリューマップです。
myActivity?.AddEvent(new("Init"));
...
myActivity?.AddEvent(new("End"));
var eventTags = new ActivityTagsCollection
{
{ "operation", "calculate-pi" },
{ "result", 3.14159 }
};
activity?.AddEvent(new("End Computation", DateTimeOffset.Now, eventTags));
リンク付きの Activity の作成
スパンは、因果関係のある0個以上の他のスパンにスパンリンクを介してリンクできます。 リンクは、バッチ処理において、スパンが複数の開始スパンによって開始され、各開始スパンがバッチで処理される個々の受信項目を表す場合に使用できます。
var links = new List<ActivityLink>
{
new ActivityLink(activityContext1),
new ActivityLink(activityContext2),
new ActivityLink(activityContext3)
};
var activity = MyActivitySource.StartActivity(
ActivityKind.Internal,
name: "activity-with-links",
links: links);
Activity ステータスの設定
ステータスはスパンに設定でき、通常はスパンが正常に完了しなかったことを示すために使用されます。
つまりErrorです。
デフォルトでは、すべてのスパンはUnsetであり、これはスパンがエラーなく完了したことを意味します。
Okステータスは、デフォルトのUnset(つまり「エラーなし」)のままにするのではなく、スパンを明示的に成功としてマークする必要がある場合に予約されています。
ステータスは、スパンが終了する前であればいつでも設定できます。
例外が発生したときにそれを記録するのはよい方法です。 スパンステータスの設定と合わせて行うことが推奨されます。
private int rollOnce()
{
using (var childActivity = activitySource.StartActivity("rollOnce"))
{
int result;
try
{
result = Random.Shared.Next(min, max + 1);
childActivity?.SetTag("dicelib.rolled", result);
}
catch (Exception ex)
{
childActivity?.SetStatus(ActivityStatusCode.Error, "Something bad happened!");
childActivity?.AddException(ex);
throw;
}
return result;
}
}
次のステップ
手動計装をセットアップした後は、計装ライブラリの使用を検討してください。 計装ライブラリはその名の通り、使用しているライブラリを計装し、受信および送信 HTTP リクエストなどのスパン(Activity)を生成します。
また、アプリのテレメトリーデータを1つ以上のテレメトリーバックエンドにエクスポートするために、適切なエクスポーターを設定することも必要です。
.NET の自動計装もチェックできます。 こちらは現在ベータ版です。
フィードバック
このページは役に立ちましたか?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!