Udostępnij przez


Metryki generowane przez źródło z silnie typizowanymi tagami

Nowoczesne aplikacje platformy .NET mogą przechwytywać metryki przy użyciu interfejsu API System.Diagnostics.Metrics. Te metryki często zawierają dodatkowy kontekst w postaci par klucz-wartość nazywanych tagami (czasami nazywane wymiarami w systemach telemetrycznych). W tym artykule pokazano, jak używać generatora kodu w czasie kompilacji do definiowania silnie typowanych tagów metryk (TagNames) oraz typów i metod zapisywania metryk. Korzystając z silnie typowanych tagów, można wyeliminować powtarzalny kod szablonowy i upewnić się, że powiązane metryki współdzielą ten sam zestaw nazw tagów, zapewniając bezpieczeństwo czasu kompilacji. Główną zaletą tego podejścia jest poprawa produktywności deweloperów i bezpieczeństwa typów.

Notatka

W kontekście metryk tag jest czasami nazywany również "wymiarem". W tym artykule użyto tagu w celu zapewnienia przejrzystości i spójności z terminologią metryk platformy .NET.

Wprowadzenie

Aby rozpocząć, zainstaluj 📦 pakiet NuGet Microsoft.Extensions.Telemetry.Abstractions :

dotnet add package Microsoft.Extensions.Telemetry.Abstractions

Aby uzyskać więcej informacji, zobacz dotnet add package lub Zarządzanie zależnościami pakietów w aplikacjach platformy .NET.

Wartości domyślne i dostosowywanie nazwy tagu

Domyślnie generator kodu źródłowego uzyskuje nazwy tagów metrycznych z nazw pól i właściwości klasy tagów. Innymi słowy, każde pole publiczne lub właściwość w silnie typiowanym obiekcie tagu staje się domyślnie nazwą tagu. Można to zastąpić przy użyciu TagNameAttribute w polu lub właściwości, aby określić niestandardową nazwę tagu. W poniższych przykładach zobaczysz oba podejścia do działania.

Przykład 1. Metryka podstawowa z pojedynczym tagiem

W poniższym przykładzie przedstawiono prostą metrykę licznika z jednym tagiem. W tym scenariuszu chcemy zliczyć liczbę przetworzonych żądań i sklasyfikować je według tagu 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);
}

W poprzednim kodzie RequestTags jest silnie typizowaną strukturą tagu z pojedynczą właściwością Region. Metoda CreateRequestCount jest oznaczona elementem CounterAttribute<T>, gdzie T jest int, co oznacza, że generuje Counter instrument, który śledzi int wartości. Atrybut odwołuje się do typeof(RequestTags), co oznacza, że licznik używa tagów zdefiniowanych w RequestTags podczas rejestrowania metryk. Generator źródłowy tworzy silnie typizowaną klasę instrumentu (o nazwie RequestCount) z metodą przyjmującą Add wartość całkowitą i RequestTags obiekt.

Aby użyć wygenerowanej metryki, utwórz Meter i zarejestruj pomiary, jak pokazano poniżej:

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);

W tym przykładzie użycia wywołanie MyMetrics.CreateRequestCount(meter) tworzy licznik (za pośrednictwem Meter) i zwraca obiekt metryki RequestCount. Gdy wywołujesz requestCountMetric.Add(1, tags), system metryczny rejestruje liczbę 1 skojarzoną z tagiem Region="NorthAmerica". Można ponownie użyć obiektu RequestTags lub można utworzyć nowe, aby zliczać wyniki dla różnych regionów, a nazwa tagu Region będzie spójnie stosowana do każdego pomiaru.

Przykład 2: Metryka z zagnieżdżonymi obiektami tagów

W przypadku bardziej złożonych scenariuszy można zdefiniować klasy tagów, które obejmują wiele tagów, zagnieżdżone obiekty, a nawet dziedziczone właściwości. Dzięki temu grupa powiązanych metryk skutecznie współużytkuje wspólny zestaw tagów. W następnym przykładzie zdefiniujesz zestaw klas tagów i użyjesz ich dla trzech różnych metryk:

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"
}

Powyższy kod definiuje dziedziczenie metryki i kształty obiektów. Poniższy kod pokazuje, jak używać tych kształtów z generatorem, jak pokazano w Metric klasie:

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);
}

W tym przykładzie MetricTags jest klasą tagu, która dziedziczy z MetricParentTags, a także zawiera zagnieżdżony obiekt tagu (MetricChildTags) i zagnieżdżoną strukturę (MetricTagsStruct). Właściwości tagu przedstawiają zarówno domyślne, jak i dostosowane nazwy tagów:

  • Pole Dim1 w MetricTags ma atrybut [TagName("Dim1DimensionName")], więc jego nazwa tagu będzie "Dim1DimensionName".
  • Właściwość Operation nie ma atrybutu, więc jego nazwa tagu jest domyślnie ustawiona na "Operation".
  • W MetricParentTagswłaściwość ParentOperationName jest zastępowana niestandardową nazwą tagu "DimensionNameOfParentOperation".
  • Zagnieżdżona klasa MetricChildTags definiuje właściwość Dim2 (bez atrybutu, nazwy tagu "Dim2").
  • Struktura MetricTagsStruct definiuje pole Dim3 (nazwa tagu "Dim3").

Wszystkie trzy definicje metryk CreateLatency, CreateTotalCounti CreateTotalFailures używają MetricTags jako typu obiektu tagu. Oznacza to, że wygenerowane typy metryk (Latency, TotalCounti TotalFailures) będą oczekiwać wystąpienia MetricTags podczas rejestrowania danych. Każdy z tych metryk będzie miał ten sam zestaw nazw tagów:Dim1DimensionName, Operation, Dim2, Dim3i DimensionNameOfParentOperation.

Poniższy kod przedstawia sposób tworzenia i używania tych metryk w klasie:

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);
        }
    }
}

W poprzedniej metodzie MyClass.DoWork obiekt MetricTags jest wypełniany wartościami dla każdego tagu. Ten pojedynczy obiekt tags jest następnie przekazywany do wszystkich trzech instrumentów podczas rejestrowania danych. Metryka Latency (histogram) rejestruje czas, który upłynął, oraz liczniki (TotalCount i TotalFailures) rejestrują liczby wystąpień. Ponieważ wszystkie metryki współużytkują ten sam typ obiektu tagu, tagi (Dim1DimensionName, Operation, Dim2, Dim3, DimensionNameOfParentOperation) są obecne dla każdej miary.

Określanie jednostek

Począwszy od platformy .NET 10.2, możesz opcjonalnie określić jednostkę miary dla metryk przy użyciu parametru Unit . Pomaga to określić kontekst miar metryki (na przykład "sekundy", "bajty" i "żądania"). Jednostka jest przekazywana do bazowego Meter podczas tworzenia instrumentu.

Poniższy kod pokazuje, jak używać generatora z typami pierwotnymi z określonymi jednostkami:

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);
}

Zagadnienia dotyczące wydajności

Używanie silnie typowanych tagów poprzez generowanie źródła nie wprowadza żadnych dodatkowych kosztów w porównaniu z bezpośrednim użyciem metryk. Jeśli chcesz jeszcze bardziej zminimalizować alokacje dla metryk o bardzo wysokiej częstotliwości, rozważ zdefiniowanie obiektu tagu jako struct (typu wartości) zamiast class. Użycie struct jako obiektu tagu może zapobiec alokacjom na stercie podczas rejestrowania metryk, ponieważ tagi są przekazywane przez wartość.

Wymagania dotyczące metody metryki wygenerowanej

Podczas definiowania metody fabryczne metryk (metody częściowe oznaczone jako [Counter], [Histogram]itd.), generator kodu nakłada kilka wymagań.

  • Każda metoda musi być public static partial (aby generator źródłowy dostarczył implementację).
  • Zwracany typ każdej metody częściowej musi być unikatowy (aby generator mógł utworzyć unikatowo nazwany typ metryki).
  • Nazwa metody nie powinna rozpoczynać się od podkreślenia (_), a nazwy parametrów nie powinny zaczynać się od podkreślenia.
  • Pierwszy parametr musi być Meter (jest to wystąpienie miernika użyte do utworzenia bazowego instrumentu).
  • Metody nie mogą być ogólne i nie mogą mieć parametrów ogólnych.
  • Właściwości tagu w klasie tagów mogą być tylko typu string lub enum. W przypadku innych typów (na przykład bool lub typów liczbowych) przekonwertuj wartość na ciąg przed przypisaniem go do obiektu tagu.

Przestrzeganie tych wymagań gwarantuje, że generator źródła może pomyślnie utworzyć typy i metody metryk.

Zobacz też