次の方法で共有


厳密に型指定されたタグを持つソース生成メトリック

最新の .NET アプリケーションでは、 System.Diagnostics.Metrics API を使用してメトリックをキャプチャできます。 多くの場合、これらのメトリックには 、タグ と呼ばれるキーと値のペアの形式で追加のコンテキストが含まれます (テレメトリ システムでは ディメンション と呼ばれることもあります)。 この記事では、コンパイル時ソース ジェネレーターを使用して、 厳密に型指定されたメトリック タグ (TagNames) とメトリック記録の種類とメソッドを定義する方法について説明します。 厳密に型指定されたタグを使用することで、繰り返し発生する定型コードを排除し、関連するメトリックがコンパイル時の安全性と同じタグ名のセットを共有するようにします。 このアプローチの主な利点は、開発者の生産性とタイプ セーフの向上です。

メトリックのコンテキストでは、タグは "ディメンション" とも呼ばれます。この記事では、.NET メトリックの用語を明確にし、一貫性を保つための "タグ" を使用します。

概要

開始するには、 📦 Microsoft.Extensions.Telemetry.Abstractions NuGet パッケージをインストールします。

dotnet add package Microsoft.Extensions.Telemetry.Abstractions

詳しくは、「dotnet add package」または「.NET アプリケーションでパッケージの依存関係を管理する」をご覧ください。

タグ名の既定値とカスタマイズ

既定では、ソース ジェネレーターは、タグ クラスのフィールド名とプロパティ名からメトリック タグ名を派生させます。 つまり、厳密に型指定されたタグ オブジェクト内の各パブリック フィールドまたはプロパティは、既定でタグ名になります。 これをオーバーライドするには、フィールドまたはプロパティの TagNameAttribute を使用してカスタム タグ名を指定します。 次の例では、両方の方法が動作しています。

例 1: 1 つのタグを持つ基本的なメトリック

次の例は、1 つのタグを持つ単純なカウンター メトリックを示しています。 このシナリオでは、処理された要求の数をカウントし、 Region タグで分類します。

public struct RequestTags
{
    public string Region { get; set; }
}

public static partial class MyMetrics
{
    [Counter<int>(typeof(RequestTags))]
    public static partial RequestCount CreateRequestCount(Meter meter);
}

上記のコードでは、 RequestTags は厳密に型指定されたタグ構造体であり、1 つのプロパティ RegionCreateRequestCountメソッドは、CounterAttribute<T>Tであるintでマークされ、Counter値を追跡するintインストルメントが生成されることを示します。 属性は typeof(RequestTags)参照します。つまり、カウンターはメトリックを記録するときに RequestTags で定義されたタグを使用します。 ソース ジェネレーターは、整数値とRequestCount オブジェクトを受け取るAdd メソッドを使用して、厳密に型指定された (RequestTags という名前の) インストルメント クラスを生成します。

生成されたメトリックを使用するには、次のように Meter を作成し、測定値を記録します。

Meter meter = new("MyCompany.MyApp", "1.0");
RequestCount requestCountMetric = MyMetrics.CreateRequestCount(meter);

// Create a tag object with the relevant tag value
var tags = new RequestTags { Region = "NorthAmerica" };

// Record a metric value with the associated tag
requestCountMetric.Add(1, tags);

この使用例では、 MyMetrics.CreateRequestCount(meter) を呼び出すと、( Meterを介して) カウンター インストルメントが作成され、 RequestCount メトリック オブジェクトが返されます。 requestCountMetric.Add(1, tags)を呼び出すと、メトリック システムはタグ Region="NorthAmerica"に関連付けられた 1 のカウントを記録します。 RequestTagsオブジェクトを再利用したり、新しいオブジェクトを作成して異なるリージョンのカウントを記録したりすることができ、タグ名Regionは一貫してすべての測定に適用されます。

例 2: 入れ子になったタグ オブジェクトを含むメトリック

より複雑なシナリオでは、複数のタグ、入れ子になったオブジェクト、または継承されたプロパティを含むタグ クラスを定義できます。 これにより、関連するメトリックのグループが、一般的なタグのセットを効果的に共有できます。 次の例では、タグ クラスのセットを定義し、3 つの異なるメトリックに使用します。

using Microsoft.Extensions.Diagnostics.Metrics;

namespace MetricsGen;

public class MetricTags : MetricParentTags
{
    [TagName("Dim1DimensionName")]
    public string? Dim1;                      // custom tag name via attribute
    public Operations Operation { get; set; } // tag name defaults to "Operation"
    public MetricChildTags? ChildTagsObject { get; set; }
}

public enum Operations
{
    Unknown = 0,
    Operation1 = 1,
}

public class MetricParentTags
{
    [TagName("DimensionNameOfParentOperation")]
    public string? ParentOperationName { get; set; }  // custom tag name via attribute
    public MetricTagsStruct ChildTagsStruct { get; set; }
}

public class MetricChildTags
{
    public string? Dim2 { get; set; }  // tag name defaults to "Dim2"
}

public struct MetricTagsStruct
{
    public string Dim3 { get; set; }   // tag name defaults to "Dim3"
}

上記のコードでは、メトリックの継承とオブジェクトの形状を定義します。 次のコードは、 Metric クラスに示すように、ジェネレーターでこれらの図形を使用する方法を示しています。

using System.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics;

public static partial class Metric
{
    [Histogram<long>(typeof(MetricTags))]
    public static partial Latency CreateLatency(Meter meter);

    [Counter<long>(typeof(MetricTags))]
    public static partial TotalCount CreateTotalCount(Meter meter);

    [Counter<int>(typeof(MetricTags))]
    public static partial TotalFailures CreateTotalFailures(Meter meter);
}

この例では、 MetricTags は、 MetricParentTags から継承し、入れ子になったタグ オブジェクト (MetricChildTags) と入れ子になった構造体 (MetricTagsStruct) も含むタグ クラスです。 タグ プロパティは、既定のタグ名とカスタマイズされたタグ名の両方を示しています。

  • Dim1MetricTags フィールドには[TagName("Dim1DimensionName")]属性があるため、タグ名が"Dim1DimensionName"されます。
  • Operation プロパティには属性がないため、タグ名の既定値は "Operation" です。
  • MetricParentTagsでは、ParentOperationName プロパティはカスタム タグ名"DimensionNameOfParentOperation"でオーバーライドされます。
  • 入れ子になった MetricChildTags クラスは、 Dim2 プロパティ (属性なし、タグ名 "Dim2") を定義します。
  • MetricTagsStruct構造体は、Dim3 フィールド (タグ名"Dim3") を定義します。

3 つのメトリック定義 CreateLatencyCreateTotalCountCreateTotalFailures はすべて、タグ オブジェクトの種類として MetricTags を使用します。 つまり、生成されたメトリックの種類 (LatencyTotalCountTotalFailures) はすべて、データの記録時に MetricTags インスタンスを想定します。 これらの各メトリックには、同じタグ名のセットがあります:Dim1DimensionNameOperationDim2Dim3、および DimensionNameOfParentOperation

次のコードは、クラスでこれらのメトリックを作成して使用する方法を示しています。

internal class MyClass
{
    private readonly Latency _latencyMetric;
    private readonly TotalCount _totalCountMetric;
    private readonly TotalFailures _totalFailuresMetric;

    public MyClass(Meter meter)
    {
        // Create metric instances using the source-generated factory methods
        _latencyMetric = Metric.CreateLatency(meter);
        _totalCountMetric = Metric.CreateTotalCount(meter);
        _totalFailuresMetric = Metric.CreateTotalFailures(meter);
    }

    public void DoWork()
    {
        var startingTimestamp = Stopwatch.GetTimestamp();
        bool requestSuccessful = true;
        // Perform some operation to measure
        var elapsedTime = Stopwatch.GetElapsedTime(startingTimestamp);

        // Create a tag object with values for all tags
        var tags = new MetricTags
        {
            Dim1 = "Dim1Value",
            Operation = Operations.Operation1,
            ParentOperationName = "ParentOpValue",
            ChildTagsObject = new MetricChildTags
            {
                Dim2 = "Dim2Value",
            },
            ChildTagsStruct = new MetricTagsStruct
            {
                Dim3 = "Dim3Value"
            }
        };

        // Record the metric values with the associated tags
        _latencyMetric.Record(elapsedTime.ElapsedMilliseconds, tags);
        _totalCountMetric.Add(1, tags);
        if (!requestSuccessful)
        {
            _totalFailuresMetric.Add(1, tags);
        }
    }
}

前の MyClass.DoWork メソッドでは、 MetricTags オブジェクトに各タグの値が設定されています。 この単一 tags オブジェクトは、データを記録するときに、3 つのすべてのインストルメントに渡されます。 Latency メトリック (ヒストグラム) には経過時間が記録され、カウンター (TotalCountTotalFailures) の両方が発生回数を記録します。 すべてのメトリックが同じタグ オブジェクトの種類を共有するため、すべての測定値にタグ (Dim1DimensionNameOperationDim2Dim3DimensionNameOfParentOperation) が存在します。

単位の指定

.NET 10.2 以降では、必要に応じて、 Unit パラメーターを使用してメトリックの測定単位を指定できます。 これは、メトリックが測定する内容 ("seconds"、"bytes"、"requests" など) に関するコンテキストを提供するのに役立ちます。 このユニットは、インストルメントの作成時に基になる Meter に渡されます。

次のコードは、ユニットが指定されたプリミティブ型でジェネレーターを使用する方法を示しています。

public static partial class Metric
{
    [Histogram<long>(typeof(MetricTags), Unit = "ms")]
    public static partial Latency CreateLatency(Meter meter);

    [Counter<long>(typeof(MetricTags), Unit = "requests")]
    public static partial TotalCount CreateTotalCount(Meter meter);

    [Counter<int>(typeof(MetricTags), Unit = "failures")]
    public static partial TotalFailures CreateTotalFailures(Meter meter);
}

パフォーマンスに関する考慮事項

ソース生成で厳密に型指定されたタグを使用すると、メトリックを直接使用する場合と比べてオーバーヘッドは発生しません。 非常に頻度の高いメトリックの割り当てをさらに最小限に抑える必要がある場合は、タグ オブジェクトをstructではなくclass (値型) として定義することを検討してください。 タグ オブジェクトに struct を使用すると、タグが値渡しされるため、メトリックを記録するときにヒープ割り当てを回避できます。

生成されたメトリック メソッドの要件

メトリック ファクトリ メソッド ( [Counter][Histogram] などで修飾された部分メソッド) を定義する場合、ソース ジェネレーターではいくつかの要件が課されます。

  • 各メソッドは public static partial する必要があります (ソース ジェネレーターが実装を提供するため)。
  • 各部分メソッドの戻り値の型は一意である必要があります (そのため、ジェネレーターはメトリックの一意の名前付き型を作成できます)。
  • メソッド名はアンダースコア (_) で始めず、パラメーター名はアンダースコアで始めてはなりません。
  • 最初のパラメーターは Meter である必要があります (これは、基になるインストルメントの作成に使用されるメーター インスタンスです)。
  • メソッドはジェネリックにできず、ジェネリック パラメーターを持つことはできません。
  • タグ クラスのタグ プロパティは、 string 型または enum型のみです。 その他の型 ( bool 型や数値型など) の場合は、タグ オブジェクトに割り当てる前に、値を文字列に変換します。

これらの要件に従うと、ソース ジェネレーターがメトリックの種類とメソッドを正常に生成できるようになります。

こちらも参照ください