Freigeben über


Von der Quelle generierte Metriken mit stark typisierten Tags

Moderne .NET-Anwendungen können Metriken mithilfe der System.Diagnostics.Metrics API erfassen. Diese Metriken enthalten häufig zusätzlichen Kontext in Form von Schlüsselwertpaaren, die als Tags bezeichnet werden (manchmal auch als Dimensionen in Telemetriesystemen bezeichnet). In diesem Artikel wird gezeigt, wie Sie einen Kompilierungszeitquellgenerator verwenden, um stark typierte Metriktags (TagNames) und Metrikaufzeichnungstypen und -methoden zu definieren. Durch die Verwendung stark typisierter Tags vermeiden Sie sich wiederholenden Codebausteine und stellen sicher, dass verwandte Metriken den gleichen Satz von Tagnamen mit Kompilierungszeitsicherheit teilen. Der Hauptvorteil dieses Ansatzes besteht darin, die Produktivität und Typsicherheit von Entwicklern zu verbessern.

Hinweis

Im Kontext von Metriken wird ein Tag manchmal auch als "Dimension" bezeichnet. In diesem Artikel wird "Tag" für Klarheit und Konsistenz mit der Terminologie von .NET-Metriken verwendet.

Loslegen

Installieren Sie zunächst das 📦 NuGet-Paket "Microsoft.Extensions.Telemetry.Abstractions NuGet":

dotnet add package Microsoft.Extensions.Telemetry.Abstractions

Weitere Informationen finden Sie unter dotnet add package oder Verwalten von Paketabhängigkeiten in .NET-Anwendungen.

Standardvorgaben und Anpassung der Tagnamen

Standardmäßig leitet der Quellgenerator metrische Tagnamen aus den Feld- und Eigenschaftennamen Ihrer Tagklasse ab. Mit anderen Worten, jedes öffentliche Feld oder jede Eigenschaft im stark typierten Tagobjekt wird standardmäßig zu einem Tagnamen. Sie können dies überschreiben, indem Sie ein TagNameAttribute Feld oder eine Eigenschaft verwenden, um einen benutzerdefinierten Tagnamen anzugeben. In den folgenden Beispielen werden beide Ansätze in Aktion angezeigt.

Beispiel 1: Einfache Metrik mit einem einzelnen Tag

Im folgenden Beispiel wird eine einfache Zählermetrik mit einem Tag veranschaulicht. In diesem Szenario möchten wir die Anzahl der verarbeiteten Anforderungen zählen und nach einem Region Tag kategorisieren:

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

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

Im vorherigen Code RequestTags handelt es sich um eine stark typierte Tagstruktur mit einer einzelnen Eigenschaft Region. Die CreateRequestCount Methode ist mit CounterAttribute<T> markiert, wobei T ein int ist, was angibt, dass sie ein Counter Instrument generiert, das int Werte verfolgt. Das Attribut verweist auf typeof(RequestTags), was bedeutet, dass der Zähler die Tags verwendet, die in RequestTags definiert sind, wenn Metriken aufgezeichnet werden. Der Quellgenerator erzeugt eine stark typierte Instrumentklasse (benannt RequestCount) mit einer Add Methode, die ganzzahlige Werte und RequestTags Objekte akzeptiert.

Um die generierte Metrik zu verwenden, erstellen Sie eine Meter und zeichnen Sie die Messungen wie unten gezeigt auf:

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

In diesem Verwendungsbeispiel wird durch aufrufen MyMetrics.CreateRequestCount(meter) ein Zählerinstrument (über das Meter) erstellt und ein metrisches RequestCount Objekt zurückgegeben. Beim Aufrufen requestCountMetric.Add(1, tags)zeichnet das Metriksystem die Anzahl 1 auf, die dem Tag Region="NorthAmerica"zugeordnet ist. Sie können das RequestTags Objekt wiederverwenden oder neue erstellen, um die Anzahl für verschiedene Bereiche aufzuzeichnen, und der Tagname Region wird konsistent auf jede Messung angewendet.

Beispiel 2: Metrik mit geschachtelten Tagobjekten

Für komplexere Szenarien können Sie Tagklassen definieren, die mehrere Tags, geschachtelte Objekte oder sogar geerbte Eigenschaften enthalten. Auf diese Weise kann eine Gruppe verwandter Metriken effektiv einen gemeinsamen Satz von Tags gemeinsam nutzen. Im nächsten Beispiel definieren Sie einen Satz von Tagklassen und verwenden sie für drei verschiedene Metriken:

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

Der vorangehende Code definiert die metrische Vererbung und Objektformen. Der folgende Code veranschaulicht, wie diese Shapes mit dem Generator verwendet werden, wie in der Metric Klasse gezeigt:

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

In diesem Beispiel ist MetricTags eine Tagklasse, die von MetricParentTags erbt und außerdem ein geschachteltes Tagobjekt (MetricChildTags) sowie eine geschachtelte Struktur (MetricTagsStruct) enthält. Die Tageigenschaften veranschaulichen sowohl standard- als auch angepasste Tagnamen:

  • Das Dim1 Feld in MetricTags verfügt über ein [TagName("Dim1DimensionName")] Attribut, sodass der Tagname lautet "Dim1DimensionName".
  • Die Operation-Eigenschaft hat kein Attribut, sodass der Tagname standardmäßig als "Operation" gesetzt wird.
  • In MetricParentTags wird die ParentOperationName-Eigenschaft mit einem benutzerdefinierten Tagnamen "DimensionNameOfParentOperation" überschrieben.
  • Die geschachtelte MetricChildTags Klasse definiert eine Dim2 Eigenschaft (kein Attribut, Tagname "Dim2").
  • Die MetricTagsStruct Struktur definiert ein Dim3 Feld (Tagname "Dim3").

Alle drei Metrikdefinitionen CreateLatency, CreateTotalCountund CreateTotalFailures verwenden MetricTags sie als Tagobjekttyp. Dies bedeutet, dass die generierten Metriktypen (Latency, TotalCountund TotalFailures) beim Aufzeichnen von Daten eine MetricTags Instanz erwarten. Jede dieser Metriken hat denselben Satz von Tagnamen:Dim1DimensionName, Operation, , Dim2, und Dim3DimensionNameOfParentOperation.

Der folgende Code zeigt, wie Sie diese Metriken in einer Klasse erstellen und verwenden:

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

In der vorherigen MyClass.DoWork-Methode wird ein MetricTags-Objekt mit Werten für jedes Tag aufgefüllt. Dieses einzelne tags Objekt wird dann beim Aufzeichnen von Daten an alle drei Instrumente übergeben. Die Latency Metrik (ein Histogramm) zeichnet die verstrichene Zeit auf, und beide Zähler (TotalCount und TotalFailures) erfassen die Anzahl der Vorkommen. Da alle Metriken denselben Tagobjekttyp aufweisen, sind die Tags (Dim1DimensionName, Operation, Dim2, Dim3, DimensionNameOfParentOperation) für jede Messung vorhanden.

Angeben von Einheiten

Ab .NET 10.2 können Sie optional eine Maßeinheit für Ihre Metriken mithilfe des Unit Parameters angeben. Dies hilft dabei, Kontext darüber bereitzustellen, was die Metrik misst (z. B. "Sekunden", "Bytes" und "Anforderungen"). Die Einheit wird beim Erstellen des Instruments an den zugrunde liegenden Meter übergeben.

Der folgende Code veranschaulicht die Verwendung des Generators mit primitiven Typen mit angegebenen Einheiten:

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

Leistungsüberlegungen

Die Verwendung stark typierter Tags über die Quellgenerierung fügt im Vergleich zur direkten Verwendung von Metriken keinen Aufwand hinzu. Wenn Sie die Zuordnungen für sehr häufig auftretende Metriken weiter minimieren müssen, sollten Sie das Tagobjekt als struct (Werttyp) anstelle eines classWerts definieren. Die Verwendung eines struct als Tagsobjekt kann Heap-Zuordnungen beim Aufzeichnen von Metriken vermeiden, da die Tags als Wert übergeben werden.

Generierte Anforderungen an die Metrikmethode

Beim Definieren von Fabrikmethoden für Metriken (die teilweise Methoden, die mit [Counter], [Histogram] usw. versehen sind), stellt der Quellgenerator einige Anforderungen.

  • Jede Methode muss sein public static partial (damit der Quellgenerator die Implementierung bereitstellt).
  • Der Rückgabetyp jeder partiellen Methode muss eindeutig sein (sodass der Generator einen eindeutig benannten Typ für die Metrik erstellen kann).
  • Der Methodenname sollte nicht mit einem Unterstrich (_) beginnen, und Parameternamen sollten nicht mit einem Unterstrich beginnen.
  • Der erste Parameter muss ein Meter sein (dies ist die Meterinstanz, die zum Erstellen des zugrunde liegenden Instruments verwendet wird).
  • Die Methoden können nicht generisch sein und können keine generischen Parameter aufweisen.
  • Die Tageigenschaften in der Tagklasse können nur vom Typ string oder enum sein. Konvertieren Sie für andere Typen (z. B. numerische Typen) den Wert in eine Zeichenfolge, bool bevor Sie ihn dem Tagobjekt zuweisen.

Durch die Einhaltung dieser Anforderungen wird sichergestellt, dass der Quellgenerator erfolgreich die Metriktypen und -methoden produzieren kann.

Siehe auch